Merge Android U (ab/10368041)

Bug: 291102124
Merged-In: I70d1294d44bdf2657201285b3e281aecda467507
Change-Id: I3a4fa7051fb559fc3f7877ab4e5869b81b360340
diff --git a/Android.bp b/Android.bp
index c636ade..892a48a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -83,13 +83,14 @@
         "android.hardware.radio-V1.4-java",
         "android.hardware.radio-V1.5-java",
         "android.hardware.radio-V1.6-java",
-        "android.hardware.radio.config-V1-java",
-        "android.hardware.radio.data-V1-java",
-        "android.hardware.radio.messaging-V1-java",
-        "android.hardware.radio.modem-V1-java",
-        "android.hardware.radio.network-V1-java",
-        "android.hardware.radio.sim-V1-java",
-        "android.hardware.radio.voice-V1-java",
+        "android.hardware.radio.config-V2-java",
+        "android.hardware.radio.data-V2-java",
+        "android.hardware.radio.ims-V1-java",
+        "android.hardware.radio.messaging-V2-java",
+        "android.hardware.radio.modem-V2-java",
+        "android.hardware.radio.network-V2-java",
+        "android.hardware.radio.sim-V2-java",
+        "android.hardware.radio.voice-V2-java",
         "voip-common",
         "ims-common",
         "unsupportedappusage",
diff --git a/OWNERS b/OWNERS
index de9ceed..35f2818 100644
--- a/OWNERS
+++ b/OWNERS
@@ -17,12 +17,6 @@
 tnd@google.com
 xiaotonj@google.com
 
-# Temporarily reduced the owner during refactoring
-per-file SubscriptionController.java=set noparent
-per-file SubscriptionController.java=jackyu@google.com,amruthr@google.com
-per-file SubscriptionInfoUpdater.java=set noparent
-per-file SubscriptionInfoUpdater.java=jackyu@google.com,amruthr@google.com
-
 
 
 
diff --git a/README.txt b/README.txt
index 9e40b77..1a44beb 100644
--- a/README.txt
+++ b/README.txt
@@ -15,7 +15,7 @@
 implement in this directory and packages/services/Telephony. This IPC scheme
 allows us to run public API code in the calling process, while the
 telephony-related code runs in the privileged com.android.phone process. Such
-implementations include PhoneInterfaceManager, SubscriptionController and
+implementations include PhoneInterfaceManager, SubscriptionManagerService and
 others.
 
 The declaration of the com.android.phone process is in
diff --git a/proto/src/persist_atoms.proto b/proto/src/persist_atoms.proto
index 0f87e95..61e44a3 100644
--- a/proto/src/persist_atoms.proto
+++ b/proto/src/persist_atoms.proto
@@ -23,7 +23,7 @@
 
 // Holds atoms to store on persist storage in case of power cycle or process crash.
 // NOTE: using int64 rather than google.protobuf.Timestamp for timestamps simplifies implementation.
-// Next id: 53
+// Next id: 70
 message PersistAtoms {
     /* Aggregated RAT usage during the call. */
     repeated VoiceCallRatUsage voice_call_rat_usage = 1;
@@ -180,6 +180,57 @@
 
     /* Unmetered networks information. */
     repeated UnmeteredNetworks unmetered_networks = 52;
+
+    /* Outgoing Short Code SMS statistics and information. */
+    repeated OutgoingShortCodeSms outgoing_short_code_sms = 53;
+
+    /* Timestamp of last outgoing_short_code_sms pull. */
+    optional int64 outgoing_short_code_sms_pull_timestamp_millis = 54;
+
+    /* Number of time the user toggled the data switch feature since the last collection. */
+    optional int32 auto_data_switch_toggle_count = 55;
+
+    /** Snapshot of satellite controller. */
+    repeated SatelliteController satellite_controller = 58;
+
+    /* Timestamp of last satellite_controller pull. */
+    optional int64 satellite_controller_pull_timestamp_millis = 59;
+
+    /** Snapshot of satellite controller. */
+    repeated SatelliteSession satellite_session = 60;
+
+    /* Timestamp of last satellite_controller pull. */
+    optional int64 satellite_session_pull_timestamp_millis = 61;
+
+    /** Snapshot of satellite incoming datagram. */
+    repeated SatelliteIncomingDatagram satellite_incoming_datagram = 62;
+
+    /* Timestamp of last satellite_incoming_datagram pull. */
+    optional int64 satellite_incoming_datagram_pull_timestamp_millis = 63;
+
+    /** Snapshot of satellite outgoing datagram. */
+    repeated SatelliteOutgoingDatagram satellite_outgoing_datagram = 64;
+
+    /* Timestamp of last satellite_outgoing_datagram pull. */
+    optional int64 satellite_outgoing_datagram_pull_timestamp_millis = 65;
+
+    /** Snapshot of satellite provision datagram. */
+    repeated SatelliteProvision satellite_provision = 66;
+
+    /* Timestamp of last satellite_provision pull. */
+    optional int64 satellite_provision_pull_timestamp_millis = 67;
+
+    /** Snapshot of satellite SOS message recommender. */
+    repeated SatelliteSosMessageRecommender satellite_sos_message_recommender = 68;
+
+    /* Timestamp of last satellite_sos_message_recommender pull. */
+    optional int64 satellite_sos_message_recommender_pull_timestamp_millis = 69;
+
+    /* Consolidated emergency numbers list information. */
+    repeated EmergencyNumbersInfo emergency_numbers_info = 56;
+
+    /* Timestamp of last emergency number pull. */
+    optional int64 emergency_number_pull_timestamp_millis = 57;
 }
 
 // The canonical versions of the following enums live in:
@@ -223,6 +274,8 @@
     optional bool is_multiparty = 31;
     optional int32 call_duration = 32;
     optional int32 last_known_rat = 33;
+    optional int32 fold_state = 34;
+
     // Internal use only
     optional int64 setup_begin_millis = 10001;
 }
@@ -250,6 +303,7 @@
     optional int32 carrier_id = 13;
     optional int64 message_id = 14;
     optional int32 count = 15;
+    optional bool is_managed_profile = 16;
 
     // Internal use only
     optional int32 hashCode = 10001;
@@ -271,6 +325,9 @@
     optional int32 retry_id = 13;
     optional int64 interval_millis = 14;
     optional int32 count = 15;
+    optional int32 send_error_code = 16;
+    optional int32 network_error_code = 17;
+    optional bool is_managed_profile = 18;
 
     // Internal use only
     optional int32 hashCode = 10001;
@@ -319,6 +376,8 @@
     optional int32 carrier_id = 8;
     optional int64 total_time_millis = 9; // Duration needs to be rounded when pulled
     optional bool is_emergency_only = 10;
+    optional bool is_internet_pdn_up = 11;
+    optional int32 fold_state = 12;
 
     // Internal use only
     optional int64 last_used_millis = 10001;
@@ -524,3 +583,92 @@
     optional int32 xml_version = 2;
     optional int32 short_code_sms_count = 3;
 }
+
+message SatelliteController {
+    optional int32 count_of_satellite_service_enablements_success = 1;
+    optional int32 count_of_satellite_service_enablements_fail = 2;
+    optional int32 count_of_outgoing_datagram_success = 3;
+    optional int32 count_of_outgoing_datagram_fail = 4;
+    optional int32 count_of_incoming_datagram_success = 5;
+    optional int32 count_of_incoming_datagram_fail = 6;
+    optional int32 count_of_datagram_type_sos_sms_success = 7;
+    optional int32 count_of_datagram_type_sos_sms_fail = 8;
+    optional int32 count_of_datagram_type_location_sharing_success = 9;
+    optional int32 count_of_datagram_type_location_sharing_fail = 10;
+    optional int32 count_of_provision_success = 11;
+    optional int32 count_of_provision_fail = 12;
+    optional int32 count_of_deprovision_success = 13;
+    optional int32 count_of_deprovision_fail = 14;
+    optional int32 total_service_uptime_sec = 15;
+    optional int32 total_battery_consumption_percent = 16;
+    optional int32 total_battery_charged_time_sec = 17;
+}
+
+message SatelliteSession {
+    optional int32 satellite_service_initialization_result = 1;
+    optional int32 satellite_technology = 2;
+    optional int32 count = 3;
+}
+
+message SatelliteIncomingDatagram {
+    optional int32 result_code = 1;
+    optional int32 datagram_size_bytes = 2;
+    optional int64 datagram_transfer_time_millis = 3;
+}
+
+message SatelliteOutgoingDatagram {
+    optional int32 datagram_type = 1;
+    optional int32 result_code = 2;
+    optional int32 datagram_size_bytes = 3;
+    optional int64 datagram_transfer_time_millis = 4;
+}
+
+message SatelliteProvision {
+    optional int32 result_code = 1;
+    optional int32 provisioning_time_sec = 2;
+    optional bool is_provision_request = 3;
+    optional bool is_canceled = 4;
+}
+
+message SatelliteSosMessageRecommender {
+    optional bool is_display_sos_message_sent = 1;
+    optional int32 count_of_timer_started = 2;
+    optional bool is_ims_registered = 3;
+    optional int32 cellular_service_state = 4;
+    optional int32 count = 5;
+}
+
+message EmergencyNumbersInfo {
+    enum ServiceCategory {
+        EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED = 0;
+        EMERGENCY_SERVICE_CATEGORY_POLICE = 1;
+        EMERGENCY_SERVICE_CATEGORY_AMBULANCE = 2;
+        EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE = 3;
+        EMERGENCY_SERVICE_CATEGORY_MARINE_GUARD = 4;
+        EMERGENCY_SERVICE_CATEGORY_MOUNTAIN_RESCUE = 5;
+        EMERGENCY_SERVICE_CATEGORY_MIEC = 6;
+        EMERGENCY_SERVICE_CATEGORY_AIEC = 7;
+    }
+    enum Source {
+        EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING = 0;
+        EMERGENCY_NUMBER_SOURCE_SIM = 1;
+        EMERGENCY_NUMBER_SOURCE_DATABASE = 2;
+        EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG = 3;
+        EMERGENCY_NUMBER_SOURCE_DEFAULT = 4;
+    }
+    enum CallRoute {
+        EMERGENCY_CALL_ROUTE_UNKNOWN = 0;
+        EMERGENCY_CALL_ROUTE_EMERGENCY = 1;
+        EMERGENCY_CALL_ROUTE_NORMAL = 2;
+    }
+    optional bool is_db_version_ignored = 1;
+    optional int32 asset_version = 2;
+    optional int32 ota_version = 3;
+    optional string number = 4;
+    optional string country_iso = 5;
+    optional string mnc = 6;
+    optional CallRoute route = 7;
+    repeated string urns = 8;
+    repeated ServiceCategory service_categories = 9;
+    repeated Source sources = 10;
+}
diff --git a/src/java/com/android/internal/telephony/BaseCommands.java b/src/java/com/android/internal/telephony/BaseCommands.java
index 972884a..b8de975 100644
--- a/src/java/com/android/internal/telephony/BaseCommands.java
+++ b/src/java/com/android/internal/telephony/BaseCommands.java
@@ -17,6 +17,8 @@
 
 package com.android.internal.telephony;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.AsyncResult;
@@ -26,6 +28,7 @@
 import android.os.Registrant;
 import android.os.RegistrantList;
 import android.telephony.Annotation.RadioPowerState;
+import android.telephony.BarringInfo;
 import android.telephony.TelephonyManager;
 import android.telephony.emergency.EmergencyNumber;
 
@@ -114,6 +117,17 @@
     protected RegistrantList mBarringInfoChangedRegistrants = new RegistrantList();
     protected RegistrantList mSimPhonebookChangedRegistrants = new RegistrantList();
     protected RegistrantList mSimPhonebookRecordsReceivedRegistrants = new RegistrantList();
+    protected RegistrantList mEmergencyNetworkScanRegistrants = new RegistrantList();
+    protected RegistrantList mConnectionSetupFailureRegistrants = new RegistrantList();
+    protected RegistrantList mNotifyAnbrRegistrants = new RegistrantList();
+    protected RegistrantList mTriggerImsDeregistrationRegistrants = new RegistrantList();
+    protected RegistrantList mPendingSatelliteMessageCountRegistrants = new RegistrantList();
+    protected RegistrantList mNewSatelliteMessagesRegistrants = new RegistrantList();
+    protected RegistrantList mSatelliteMessagesTransferCompleteRegistrants = new RegistrantList();
+    protected RegistrantList mSatellitePointingInfoChangedRegistrants = new RegistrantList();
+    protected RegistrantList mSatelliteModeChangedRegistrants = new RegistrantList();
+    protected RegistrantList mSatelliteRadioTechnologyChangedRegistrants = new RegistrantList();
+    protected RegistrantList mSatelliteProvisionStateChangedRegistrants = new RegistrantList();
 
     @UnsupportedAppUsage
     protected Registrant mGsmSmsRegistrant;
@@ -160,6 +174,8 @@
     // Cache last emergency number list indication from radio
     private final List<EmergencyNumber> mLastEmergencyNumberListIndication = new ArrayList<>();
 
+    // The last barring information received
+    protected BarringInfo mLastBarringInfo = new BarringInfo();
     // Preferred network type received from PhoneFactory.
     // This is used when establishing a connection to the
     // vendor ril so it starts up in the correct mode.
@@ -900,6 +916,7 @@
                     || mState == TelephonyManager.RADIO_POWER_UNAVAILABLE)
                     && (oldState == TelephonyManager.RADIO_POWER_ON)) {
                 mOffOrNotAvailRegistrants.notifyRegistrants();
+                mLastBarringInfo = new BarringInfo();
             }
         }
     }
@@ -918,6 +935,12 @@
         }
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public @NonNull BarringInfo getLastBarringInfo() {
+        return mLastBarringInfo;
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -1132,4 +1155,133 @@
     @Override
     public void updateSimPhonebookRecord(SimPhonebookRecord phonebookRecord, Message result) {
     }
+
+    /**
+     * Register for Emergency network scan result.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    @Override
+    public void registerForEmergencyNetworkScan(Handler h, int what, Object obj) {
+        mEmergencyNetworkScanRegistrants.add(h, what, obj);
+    }
+
+    /**
+     * Unregister for Emergency network scan result.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    @Override
+    public void unregisterForEmergencyNetworkScan(Handler h) {
+        mEmergencyNetworkScanRegistrants.remove(h);
+    }
+
+    @Override
+    public void registerForConnectionSetupFailure(Handler h, int what, Object obj) {
+        mConnectionSetupFailureRegistrants.addUnique(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForConnectionSetupFailure(Handler h) {
+        mConnectionSetupFailureRegistrants.remove(h);
+    }
+
+    @Override
+    public void registerForNotifyAnbr(Handler h, int what, Object obj) {
+        mNotifyAnbrRegistrants.addUnique(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForNotifyAnbr(Handler h) {
+        mNotifyAnbrRegistrants.remove(h);
+    }
+
+    @Override
+    public void registerForTriggerImsDeregistration(Handler h, int what, Object obj) {
+        mTriggerImsDeregistrationRegistrants.add(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForTriggerImsDeregistration(Handler h) {
+        mTriggerImsDeregistrationRegistrants.remove(h);
+    }
+
+    @Override
+    public void registerForPendingSatelliteMessageCount(
+            @NonNull Handler h, int what, @Nullable Object obj) {
+        mPendingSatelliteMessageCountRegistrants.add(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForPendingSatelliteMessageCount(@NonNull Handler h) {
+        mPendingSatelliteMessageCountRegistrants.remove(h);
+    }
+
+    @Override
+    public void registerForNewSatelliteMessages(
+            @NonNull Handler h, int what, @Nullable Object obj) {
+        mNewSatelliteMessagesRegistrants.add(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForNewSatelliteMessages(@NonNull Handler h) {
+        mNewSatelliteMessagesRegistrants.remove(h);
+    }
+
+    @Override
+    public void registerForSatelliteMessagesTransferComplete(@NonNull Handler h,
+            int what, @Nullable Object obj) {
+        mSatelliteMessagesTransferCompleteRegistrants.add(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForSatelliteMessagesTransferComplete(@NonNull Handler h) {
+        mSatelliteMessagesTransferCompleteRegistrants.remove(h);
+    }
+
+    @Override
+    public void registerForSatellitePointingInfoChanged(@NonNull Handler h,
+            int what, @Nullable Object obj) {
+        mSatellitePointingInfoChangedRegistrants.add(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForSatellitePointingInfoChanged(@NonNull Handler h) {
+        mSatellitePointingInfoChangedRegistrants.remove(h);
+    }
+
+    @Override
+    public void registerForSatelliteModeChanged(@NonNull Handler h,
+            int what, @Nullable Object obj) {
+        mSatelliteModeChangedRegistrants.add(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForSatelliteModeChanged(@NonNull Handler h) {
+        mSatelliteModeChangedRegistrants.remove(h);
+    }
+
+    @Override
+    public void registerForSatelliteRadioTechnologyChanged(@NonNull Handler h,
+            int what, @Nullable Object obj) {
+        mSatelliteRadioTechnologyChangedRegistrants.add(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForSatelliteRadioTechnologyChanged(@NonNull Handler h) {
+        mSatelliteRadioTechnologyChangedRegistrants.remove(h);
+    }
+
+    @Override
+    public void registerForSatelliteProvisionStateChanged(@NonNull Handler h,
+            int what, @Nullable Object obj) {
+        mSatelliteProvisionStateChangedRegistrants.add(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForSatelliteProvisionStateChanged(@NonNull Handler h) {
+        mSatelliteProvisionStateChangedRegistrants.remove(h);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/CallWaitingController.java b/src/java/com/android/internal/telephony/CallWaitingController.java
new file mode 100644
index 0000000..49940fc
--- /dev/null
+++ b/src/java/com/android/internal/telephony/CallWaitingController.java
@@ -0,0 +1,684 @@
+/*
+ * Copyright (C) 2021 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 android.telephony.CarrierConfigManager.ImsSs.CALL_WAITING_SYNC_FIRST_CHANGE;
+import static android.telephony.CarrierConfigManager.ImsSs.CALL_WAITING_SYNC_FIRST_POWER_UP;
+import static android.telephony.CarrierConfigManager.ImsSs.CALL_WAITING_SYNC_IMS_ONLY;
+import static android.telephony.CarrierConfigManager.ImsSs.CALL_WAITING_SYNC_NONE;
+import static android.telephony.CarrierConfigManager.ImsSs.CALL_WAITING_SYNC_USER_CHANGE;
+import static android.telephony.CarrierConfigManager.ImsSs.KEY_TERMINAL_BASED_CALL_WAITING_DEFAULT_ENABLED_BOOL;
+import static android.telephony.CarrierConfigManager.ImsSs.KEY_TERMINAL_BASED_CALL_WAITING_SYNC_TYPE_INT;
+import static android.telephony.CarrierConfigManager.ImsSs.KEY_UT_TERMINAL_BASED_SERVICES_INT_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsSs.SUPPLEMENTARY_SERVICE_CW;
+
+import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_NONE;
+import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_VOICE;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.telephony.Rlog;
+
+import java.io.PrintWriter;
+
+/**
+ * Controls the change of the user setting of the call waiting service
+ */
+public class CallWaitingController extends Handler {
+
+    public static final String LOG_TAG = "CallWaitingCtrl";
+    private static final boolean DBG = false; /* STOPSHIP if true */
+
+    // Terminal-based call waiting is not supported. */
+    public static final int TERMINAL_BASED_NOT_SUPPORTED = -1;
+    // Terminal-based call waiting is supported but not activated. */
+    public static final int TERMINAL_BASED_NOT_ACTIVATED = 0;
+    // Terminal-based call waiting is supported and activated. */
+    public static final int TERMINAL_BASED_ACTIVATED = 1;
+
+    private static final int EVENT_SET_CALL_WAITING_DONE = 1;
+    private static final int EVENT_GET_CALL_WAITING_DONE = 2;
+    private static final int EVENT_REGISTERED_TO_NETWORK = 3;
+
+    // Class to pack mOnComplete object passed by the caller
+    private static class Cw {
+        final boolean mEnable;
+        final Message mOnComplete;
+        final boolean mImsRegistered;
+
+        Cw(boolean enable, boolean imsRegistered, Message onComplete) {
+            mEnable = enable;
+            mOnComplete = onComplete;
+            mImsRegistered = imsRegistered;
+        }
+    }
+
+    @VisibleForTesting
+    public static final String PREFERENCE_TBCW = "terminal_based_call_waiting";
+    @VisibleForTesting
+    public static final String KEY_SUB_ID = "subId";
+    @VisibleForTesting
+    public static final String KEY_STATE = "state";
+    @VisibleForTesting
+    public static final String KEY_CS_SYNC = "cs_sync";
+
+    private final CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener =
+            (slotIndex, subId, carrierId, specificCarrierId) -> onCarrierConfigurationChanged(
+                    slotIndex);
+
+    private boolean mSupportedByImsService = false;
+    private boolean mValidSubscription = false;
+
+    // The user's last setting of terminal-based call waiting
+    private int mCallWaitingState = TERMINAL_BASED_NOT_SUPPORTED;
+
+    private int mSyncPreference = CALL_WAITING_SYNC_NONE;
+    private int mLastSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+    private boolean mCsEnabled = false;
+    private boolean mRegisteredForNetworkAttach = false;
+    private boolean mImsRegistered = false;
+
+    private final GsmCdmaPhone mPhone;
+    private final ServiceStateTracker mSST;
+    private final Context mContext;
+
+    // Constructors
+    public CallWaitingController(GsmCdmaPhone phone) {
+        mPhone = phone;
+        mSST = phone.getServiceStateTracker();
+        mContext = phone.getContext();
+    }
+
+    private void initialize() {
+        CarrierConfigManager ccm = mContext.getSystemService(CarrierConfigManager.class);
+        if (ccm != null) {
+            // Callback directly handle carrier config change should be executed in handler thread
+            ccm.registerCarrierConfigChangeListener(this::post, mCarrierConfigChangeListener);
+        } else {
+            loge("CarrierConfigLoader is not available.");
+        }
+
+        int phoneId = mPhone.getPhoneId();
+        int subId = mPhone.getSubId();
+        SharedPreferences sp =
+                mContext.getSharedPreferences(PREFERENCE_TBCW, Context.MODE_PRIVATE);
+        mLastSubId = sp.getInt(KEY_SUB_ID + phoneId, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        mCallWaitingState = sp.getInt(KEY_STATE + subId, TERMINAL_BASED_NOT_SUPPORTED);
+        mSyncPreference = sp.getInt(KEY_CS_SYNC + phoneId, CALL_WAITING_SYNC_NONE);
+
+        logi("initialize phoneId=" + phoneId
+                + ", lastSubId=" + mLastSubId + ", subId=" + subId
+                + ", state=" + mCallWaitingState + ", sync=" + mSyncPreference
+                + ", csEnabled=" + mCsEnabled);
+    }
+
+    /**
+     * Returns the cached user setting.
+     *
+     * Possible values are
+     * {@link #TERMINAL_BASED_NOT_SUPPORTED},
+     * {@link #TERMINAL_BASED_NOT_ACTIVATED}, and
+     * {@link #TERMINAL_BASED_ACTIVATED}.
+     *
+     * @param forCsOnly indicates the caller expects the result for CS calls only
+     */
+    @VisibleForTesting
+    public synchronized int getTerminalBasedCallWaitingState(boolean forCsOnly) {
+        if (forCsOnly && (!mImsRegistered) && mSyncPreference == CALL_WAITING_SYNC_IMS_ONLY) {
+            return TERMINAL_BASED_NOT_SUPPORTED;
+        }
+        if (!mValidSubscription) return TERMINAL_BASED_NOT_SUPPORTED;
+        return mCallWaitingState;
+    }
+
+    /**
+     * Serves the user's requests to interrogate the call waiting service
+     *
+     * @return true when terminal-based call waiting is supported, otherwise false
+     */
+    @VisibleForTesting
+    public synchronized boolean getCallWaiting(@Nullable Message onComplete) {
+        if (mCallWaitingState == TERMINAL_BASED_NOT_SUPPORTED) return false;
+
+        logi("getCallWaiting " + mCallWaitingState);
+
+        if (mSyncPreference == CALL_WAITING_SYNC_FIRST_CHANGE) {
+            // Interrogate CW in CS network
+            if (!mCsEnabled) {
+                // skip interrogation if CS is not available and IMS is registered
+                if (isCircuitSwitchedNetworkAvailable() || !isImsRegistered()) {
+                    Cw cw = new Cw(false, isImsRegistered(), onComplete);
+                    Message resp = obtainMessage(EVENT_GET_CALL_WAITING_DONE, 0, 0, cw);
+                    mPhone.mCi.queryCallWaiting(SERVICE_CLASS_NONE, resp);
+                    return true;
+                }
+            }
+        }
+
+        if (mSyncPreference == CALL_WAITING_SYNC_NONE
+                || mSyncPreference == CALL_WAITING_SYNC_FIRST_CHANGE
+                || mSyncPreference == CALL_WAITING_SYNC_FIRST_POWER_UP
+                || isSyncImsOnly()) {
+            sendGetCallWaitingResponse(onComplete);
+            return true;
+        } else if (mSyncPreference == CALL_WAITING_SYNC_USER_CHANGE
+                || mSyncPreference == CALL_WAITING_SYNC_IMS_ONLY) {
+            Cw cw = new Cw(false, isImsRegistered(), onComplete);
+            Message resp = obtainMessage(EVENT_GET_CALL_WAITING_DONE, 0, 0, cw);
+            mPhone.mCi.queryCallWaiting(SERVICE_CLASS_NONE, resp);
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Serves the user's requests to set the call waiting service
+     *
+     * @param serviceClass the target service class. Values are CommandsInterface.SERVICE_CLASS_*.
+     * @return true when terminal-based call waiting is supported, otherwise false
+     */
+    @VisibleForTesting
+    public synchronized boolean setCallWaiting(boolean enable,
+            int serviceClass, @Nullable Message onComplete) {
+        if (mCallWaitingState == TERMINAL_BASED_NOT_SUPPORTED) return false;
+
+        if ((serviceClass & SERVICE_CLASS_VOICE) != SERVICE_CLASS_VOICE) return false;
+
+        logi("setCallWaiting enable=" + enable + ", service=" + serviceClass);
+
+        if (mSyncPreference == CALL_WAITING_SYNC_FIRST_CHANGE) {
+            // Enable CW in the CS network
+            if (!mCsEnabled && enable) {
+                if (isCircuitSwitchedNetworkAvailable() || !isImsRegistered()) {
+                    Cw cw = new Cw(true, isImsRegistered(), onComplete);
+                    Message resp = obtainMessage(EVENT_SET_CALL_WAITING_DONE, 0, 0, cw);
+                    mPhone.mCi.setCallWaiting(true, serviceClass, resp);
+                    return true;
+                } else {
+                    // CS network is not available, however, IMS is registered.
+                    // Enabling the service in the CS network will be delayed.
+                    registerForNetworkAttached();
+                }
+            }
+        }
+
+        if (mSyncPreference == CALL_WAITING_SYNC_NONE
+                || mSyncPreference == CALL_WAITING_SYNC_FIRST_CHANGE
+                || mSyncPreference == CALL_WAITING_SYNC_FIRST_POWER_UP
+                || isSyncImsOnly()) {
+            updateState(
+                    enable ? TERMINAL_BASED_ACTIVATED : TERMINAL_BASED_NOT_ACTIVATED);
+
+            sendToTarget(onComplete, null, null);
+            return true;
+        } else if (mSyncPreference == CALL_WAITING_SYNC_USER_CHANGE
+                || mSyncPreference == CALL_WAITING_SYNC_IMS_ONLY) {
+            Cw cw = new Cw(enable, isImsRegistered(), onComplete);
+            Message resp = obtainMessage(EVENT_SET_CALL_WAITING_DONE, 0, 0, cw);
+            mPhone.mCi.setCallWaiting(enable, serviceClass, resp);
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case EVENT_SET_CALL_WAITING_DONE:
+                onSetCallWaitingDone((AsyncResult) msg.obj);
+                break;
+            case EVENT_GET_CALL_WAITING_DONE:
+                onGetCallWaitingDone((AsyncResult) msg.obj);
+                break;
+            case EVENT_REGISTERED_TO_NETWORK:
+                onRegisteredToNetwork();
+                break;
+            default:
+                break;
+        }
+    }
+
+    private synchronized void onSetCallWaitingDone(AsyncResult ar) {
+        if (ar.userObj == null) {
+            // For the case, CALL_WAITING_SYNC_FIRST_POWER_UP
+            if (DBG) logd("onSetCallWaitingDone to sync on network attached");
+            if (ar.exception == null) {
+                updateSyncState(true);
+            } else {
+                loge("onSetCallWaitingDone e=" + ar.exception);
+            }
+            return;
+        }
+
+        if (!(ar.userObj instanceof Cw)) {
+            // Unexpected state
+            if (DBG) logd("onSetCallWaitingDone unexpected result");
+            return;
+        }
+
+        if (DBG) logd("onSetCallWaitingDone");
+        Cw cw = (Cw) ar.userObj;
+
+        if (mSyncPreference == CALL_WAITING_SYNC_IMS_ONLY) {
+            // do not synchronize service state between CS and IMS
+            sendToTarget(cw.mOnComplete, ar.result, ar.exception);
+            return;
+        }
+
+        if (ar.exception == null) {
+            if (mSyncPreference == CALL_WAITING_SYNC_FIRST_CHANGE) {
+                // SYNC_FIRST_CHANGE implies cw.mEnable is true.
+                updateSyncState(true);
+            }
+            updateState(
+                    cw.mEnable ? TERMINAL_BASED_ACTIVATED : TERMINAL_BASED_NOT_ACTIVATED);
+        } else if (mSyncPreference == CALL_WAITING_SYNC_FIRST_CHANGE) {
+            if (cw.mImsRegistered) {
+                // IMS is registered. Do not notify error.
+                // SYNC_FIRST_CHANGE implies cw.mEnable is true.
+                updateState(TERMINAL_BASED_ACTIVATED);
+                sendToTarget(cw.mOnComplete, null, null);
+                return;
+            }
+        }
+        sendToTarget(cw.mOnComplete, ar.result, ar.exception);
+    }
+
+    private synchronized void onGetCallWaitingDone(AsyncResult ar) {
+        if (ar.userObj == null) {
+            // For the case, CALL_WAITING_SYNC_FIRST_POWER_UP
+            if (DBG) logd("onGetCallWaitingDone to sync on network attached");
+            boolean enabled = false;
+            if (ar.exception == null) {
+                //resp[0]: 1 if enabled, 0 otherwise
+                //resp[1]: bitwise ORs of SERVICE_CLASS_* constants
+                int[] resp = (int[]) ar.result;
+                if (resp != null && resp.length > 1) {
+                    enabled = (resp[0] == 1)
+                            && (resp[1] & SERVICE_CLASS_VOICE) == SERVICE_CLASS_VOICE;
+                } else {
+                    loge("onGetCallWaitingDone unexpected response");
+                }
+            } else {
+                loge("onGetCallWaitingDone e=" + ar.exception);
+            }
+            if (enabled) {
+                updateSyncState(true);
+            } else {
+                logi("onGetCallWaitingDone enabling CW service in CS network");
+                mPhone.mCi.setCallWaiting(true, SERVICE_CLASS_VOICE,
+                        obtainMessage(EVENT_SET_CALL_WAITING_DONE));
+            }
+            unregisterForNetworkAttached();
+            return;
+        }
+
+        if (!(ar.userObj instanceof Cw)) {
+            // Unexpected state
+            if (DBG) logd("onGetCallWaitingDone unexpected result");
+            return;
+        }
+
+        if (DBG) logd("onGetCallWaitingDone");
+        Cw cw = (Cw) ar.userObj;
+
+        if (mSyncPreference == CALL_WAITING_SYNC_IMS_ONLY) {
+            // do not synchronize service state between CS and IMS
+            sendToTarget(cw.mOnComplete, ar.result, ar.exception);
+            return;
+        }
+
+        if (ar.exception == null) {
+            int[] resp = (int[]) ar.result;
+            //resp[0]: 1 if enabled, 0 otherwise
+            //resp[1]: bitwise ORs of SERVICE_CLASS_
+            if (resp == null || resp.length < 2) {
+                logi("onGetCallWaitingDone unexpected response");
+                if (mSyncPreference == CALL_WAITING_SYNC_FIRST_CHANGE) {
+                    // no exception but unexpected response, local setting is preferred.
+                    sendGetCallWaitingResponse(cw.mOnComplete);
+                } else {
+                    sendToTarget(cw.mOnComplete, ar.result, ar.exception);
+                }
+                return;
+            }
+
+            boolean enabled = resp[0] == 1
+                    && (resp[1] & SERVICE_CLASS_VOICE) == SERVICE_CLASS_VOICE;
+
+            if (mSyncPreference == CALL_WAITING_SYNC_FIRST_CHANGE) {
+                updateSyncState(enabled);
+
+                if (!enabled && !cw.mImsRegistered) {
+                    // IMS is not registered, change the local setting
+                    logi("onGetCallWaitingDone CW in CS network is disabled.");
+                    updateState(TERMINAL_BASED_NOT_ACTIVATED);
+                }
+
+                // return the user setting saved
+                sendGetCallWaitingResponse(cw.mOnComplete);
+                return;
+            }
+            updateState(enabled ? TERMINAL_BASED_ACTIVATED : TERMINAL_BASED_NOT_ACTIVATED);
+        } else if (mSyncPreference == CALL_WAITING_SYNC_FIRST_CHANGE) {
+            // Got an exception
+            if (cw.mImsRegistered) {
+                // queryCallWaiting failed. However, IMS is registered. Do not notify error.
+                // return the user setting saved
+                logi("onGetCallWaitingDone get an exception, but IMS is registered");
+                sendGetCallWaitingResponse(cw.mOnComplete);
+                return;
+            }
+        }
+        sendToTarget(cw.mOnComplete, ar.result, ar.exception);
+    }
+
+    private void sendToTarget(Message onComplete, Object result, Throwable exception) {
+        if (onComplete != null) {
+            AsyncResult.forMessage(onComplete, result, exception);
+            onComplete.sendToTarget();
+        }
+    }
+
+    private void sendGetCallWaitingResponse(Message onComplete) {
+        if (onComplete != null) {
+            int serviceClass = SERVICE_CLASS_NONE;
+            if (mCallWaitingState == TERMINAL_BASED_ACTIVATED) {
+                serviceClass = SERVICE_CLASS_VOICE;
+            }
+            sendToTarget(onComplete, new int[] { mCallWaitingState, serviceClass }, null);
+        }
+    }
+
+    private synchronized void onRegisteredToNetwork() {
+        if (mCsEnabled) return;
+
+        if (DBG) logd("onRegisteredToNetwork");
+
+        mPhone.mCi.queryCallWaiting(SERVICE_CLASS_NONE,
+                obtainMessage(EVENT_GET_CALL_WAITING_DONE));
+    }
+
+    private synchronized void onCarrierConfigurationChanged(int slotIndex) {
+        if (slotIndex != mPhone.getPhoneId()) return;
+
+        int subId = mPhone.getSubId();
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            logi("onCarrierConfigChanged invalid subId=" + subId);
+
+            mValidSubscription = false;
+            unregisterForNetworkAttached();
+            return;
+        }
+
+        if (!updateCarrierConfig(subId, false /* ignoreSavedState */)) {
+            return;
+        }
+
+        logi("onCarrierConfigChanged cs_enabled=" + mCsEnabled);
+
+        if (mSyncPreference == CALL_WAITING_SYNC_FIRST_POWER_UP) {
+            if (!mCsEnabled) {
+                registerForNetworkAttached();
+            }
+        }
+    }
+
+    /**
+     * @param ignoreSavedState only used for test
+     * @return true when succeeded.
+     */
+    @VisibleForTesting
+    public boolean updateCarrierConfig(int subId, boolean ignoreSavedState) {
+        mValidSubscription = true;
+
+        PersistableBundle b =
+                CarrierConfigManager.getCarrierConfigSubset(
+                        mContext,
+                        subId,
+                        KEY_UT_TERMINAL_BASED_SERVICES_INT_ARRAY,
+                        KEY_TERMINAL_BASED_CALL_WAITING_SYNC_TYPE_INT,
+                        KEY_TERMINAL_BASED_CALL_WAITING_DEFAULT_ENABLED_BOOL);
+        if (b.isEmpty()) return false;
+
+        boolean supportsTerminalBased = false;
+        int[] services = b.getIntArray(KEY_UT_TERMINAL_BASED_SERVICES_INT_ARRAY);
+        if (services != null) {
+            for (int service : services) {
+                if (service == SUPPLEMENTARY_SERVICE_CW) {
+                    supportsTerminalBased = true;
+                }
+            }
+        }
+        int syncPreference = b.getInt(KEY_TERMINAL_BASED_CALL_WAITING_SYNC_TYPE_INT,
+                CALL_WAITING_SYNC_FIRST_CHANGE);
+        boolean activated = b.getBoolean(KEY_TERMINAL_BASED_CALL_WAITING_DEFAULT_ENABLED_BOOL);
+        int defaultState = supportsTerminalBased
+                ? (activated ? TERMINAL_BASED_ACTIVATED : TERMINAL_BASED_NOT_ACTIVATED)
+                : TERMINAL_BASED_NOT_SUPPORTED;
+        int savedState = getSavedState(subId);
+
+        if (DBG) {
+            logd("updateCarrierConfig phoneId=" + mPhone.getPhoneId()
+                    + ", subId=" + subId + ", support=" + supportsTerminalBased
+                    + ", sync=" + syncPreference + ", default=" + defaultState
+                    + ", savedState=" + savedState);
+        }
+
+        int desiredState = savedState;
+
+        if (ignoreSavedState) {
+            desiredState = defaultState;
+        } else if ((mLastSubId != subId)
+                && (syncPreference == CALL_WAITING_SYNC_FIRST_POWER_UP
+                        || syncPreference == CALL_WAITING_SYNC_FIRST_CHANGE)) {
+            desiredState = defaultState;
+        } else {
+            if (defaultState == TERMINAL_BASED_NOT_SUPPORTED) {
+                desiredState = TERMINAL_BASED_NOT_SUPPORTED;
+            } else if (savedState == TERMINAL_BASED_NOT_SUPPORTED) {
+                desiredState = defaultState;
+            }
+        }
+
+        updateState(desiredState, syncPreference, ignoreSavedState);
+        return true;
+    }
+
+    private void updateState(int state) {
+        updateState(state, mSyncPreference, false);
+    }
+
+    private void updateState(int state, int syncPreference, boolean ignoreSavedState) {
+        int subId = mPhone.getSubId();
+
+        if (mLastSubId == subId
+                && mCallWaitingState == state
+                && mSyncPreference == syncPreference
+                && (!ignoreSavedState)) {
+            return;
+        }
+
+        int phoneId = mPhone.getPhoneId();
+
+        logi("updateState phoneId=" + phoneId
+                + ", subId=" + subId + ", state=" + state
+                + ", sync=" + syncPreference + ", ignoreSavedState=" + ignoreSavedState);
+
+        SharedPreferences sp =
+                mContext.getSharedPreferences(PREFERENCE_TBCW, Context.MODE_PRIVATE);
+
+        SharedPreferences.Editor editor = sp.edit();
+        editor.putInt(KEY_SUB_ID + phoneId, subId);
+        editor.putInt(KEY_STATE + subId, state);
+        editor.putInt(KEY_CS_SYNC + phoneId, syncPreference);
+        editor.apply();
+
+        mCallWaitingState = state;
+        mLastSubId = subId;
+        mSyncPreference = syncPreference;
+        if (mLastSubId != subId) {
+            mCsEnabled = false;
+        }
+
+        mPhone.setTerminalBasedCallWaitingStatus(mCallWaitingState);
+    }
+
+    private int getSavedState(int subId) {
+        SharedPreferences sp =
+                mContext.getSharedPreferences(PREFERENCE_TBCW, Context.MODE_PRIVATE);
+        int state = sp.getInt(KEY_STATE + subId, TERMINAL_BASED_NOT_SUPPORTED);
+
+        logi("getSavedState subId=" + subId + ", state=" + state);
+
+        return state;
+    }
+
+    private void updateSyncState(boolean enabled) {
+        int phoneId = mPhone.getPhoneId();
+
+        logi("updateSyncState phoneId=" + phoneId + ", enabled=" + enabled);
+
+        mCsEnabled = enabled;
+    }
+
+    /**
+     * @return whether the service is enabled in the CS network
+     */
+    @VisibleForTesting
+    public boolean getSyncState() {
+        return mCsEnabled;
+    }
+
+    private boolean isCircuitSwitchedNetworkAvailable() {
+        logi("isCircuitSwitchedNetworkAvailable="
+                + (mSST.getServiceState().getState() == ServiceState.STATE_IN_SERVICE));
+        return mSST.getServiceState().getState() == ServiceState.STATE_IN_SERVICE;
+    }
+
+    private boolean isImsRegistered() {
+        logi("isImsRegistered " + mImsRegistered);
+        return mImsRegistered;
+    }
+
+    /**
+     * Sets the registration state of IMS service.
+     */
+    public synchronized void setImsRegistrationState(boolean registered) {
+        logi("setImsRegistrationState prev=" + mImsRegistered
+                + ", new=" + registered);
+        mImsRegistered = registered;
+    }
+
+    private void registerForNetworkAttached() {
+        logi("registerForNetworkAttached");
+        if (mRegisteredForNetworkAttach) return;
+
+        mSST.registerForNetworkAttached(this, EVENT_REGISTERED_TO_NETWORK, null);
+        mRegisteredForNetworkAttach = true;
+    }
+
+    private void unregisterForNetworkAttached() {
+        logi("unregisterForNetworkAttached");
+        if (!mRegisteredForNetworkAttach) return;
+
+        mSST.unregisterForNetworkAttached(this);
+        removeMessages(EVENT_REGISTERED_TO_NETWORK);
+        mRegisteredForNetworkAttach = false;
+    }
+
+    /**
+     * Sets whether the device supports the terminal-based call waiting.
+     * Only for test
+     */
+    @VisibleForTesting
+    public synchronized void setTerminalBasedCallWaitingSupported(boolean supported) {
+        if (mSupportedByImsService == supported) return;
+
+        logi("setTerminalBasedCallWaitingSupported " + supported);
+
+        mSupportedByImsService = supported;
+
+        if (supported) {
+            initialize();
+            onCarrierConfigurationChanged(mPhone.getPhoneId());
+        } else {
+            CarrierConfigManager ccm = mContext.getSystemService(CarrierConfigManager.class);
+            if (ccm != null && mCarrierConfigChangeListener != null) {
+                ccm.unregisterCarrierConfigChangeListener(mCarrierConfigChangeListener);
+            }
+            updateState(TERMINAL_BASED_NOT_SUPPORTED);
+        }
+    }
+
+    /**
+     * Notifies that the UE has attached to the network
+     * Only for test
+     */
+    @VisibleForTesting
+    public void notifyRegisteredToNetwork() {
+        sendEmptyMessage(EVENT_REGISTERED_TO_NETWORK);
+    }
+
+    private boolean isSyncImsOnly() {
+        return (mSyncPreference == CALL_WAITING_SYNC_IMS_ONLY && mImsRegistered);
+    }
+
+    /**
+     * Dump this instance into a readable format for dumpsys usage.
+     */
+    public void dump(PrintWriter printWriter) {
+        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
+        pw.increaseIndent();
+        pw.println("CallWaitingController:");
+        pw.println(" mSupportedByImsService=" + mSupportedByImsService);
+        pw.println(" mValidSubscription=" + mValidSubscription);
+        pw.println(" mCallWaitingState=" + mCallWaitingState);
+        pw.println(" mSyncPreference=" + mSyncPreference);
+        pw.println(" mLastSubId=" + mLastSubId);
+        pw.println(" mCsEnabled=" + mCsEnabled);
+        pw.println(" mRegisteredForNetworkAttach=" + mRegisteredForNetworkAttach);
+        pw.println(" mImsRegistered=" + mImsRegistered);
+        pw.decreaseIndent();
+    }
+
+    private void loge(String msg) {
+        Rlog.e(LOG_TAG, "[" + mPhone.getPhoneId() + "] " + msg);
+    }
+
+    private void logi(String msg) {
+        Rlog.i(LOG_TAG, "[" + mPhone.getPhoneId() + "] " + msg);
+    }
+
+    private void logd(String msg) {
+        Rlog.d(LOG_TAG, "[" + mPhone.getPhoneId() + "] " + msg);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/CarrierInfoManager.java b/src/java/com/android/internal/telephony/CarrierInfoManager.java
index 3e2baa5..863db93 100644
--- a/src/java/com/android/internal/telephony/CarrierInfoManager.java
+++ b/src/java/com/android/internal/telephony/CarrierInfoManager.java
@@ -287,16 +287,17 @@
             return;
         }
         mLastAccessResetCarrierKey = now;
-        int[] subIds = context.getSystemService(SubscriptionManager.class)
-                .getSubscriptionIds(mPhoneId);
-        if (subIds == null || subIds.length < 1) {
+
+        int subId = SubscriptionManager.getSubscriptionId(mPhoneId);
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
             Log.e(LOG_TAG, "Could not reset carrier keys, subscription for mPhoneId=" + mPhoneId);
             return;
         }
+
         final TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class)
-                .createForSubscriptionId(subIds[0]);
+                .createForSubscriptionId(subId);
         int carrierId = telephonyManager.getSimCarrierId();
-        deleteCarrierInfoForImsiEncryption(context, subIds[0], carrierId);
+        deleteCarrierInfoForImsiEncryption(context, subId, carrierId);
         Intent resetIntent = new Intent(TelephonyIntents.ACTION_CARRIER_CERTIFICATE_DOWNLOAD);
         SubscriptionManager.putPhoneIdAndSubIdExtra(resetIntent, mPhoneId);
         context.sendBroadcastAsUser(resetIntent, UserHandle.ALL);
diff --git a/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java b/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java
index fc6afc0..ab7ebc4 100644
--- a/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java
+++ b/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java
@@ -78,6 +78,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
@@ -563,10 +564,10 @@
         List<Signature> signatures = UiccAccessRule.getSignatures(pkg);
         for (Signature signature : signatures) {
             byte[] sha1 = UiccAccessRule.getCertHash(signature, SHA_1);
-            certs.add(IccUtils.bytesToHexString(sha1).toUpperCase());
+            certs.add(IccUtils.bytesToHexString(sha1).toUpperCase(Locale.ROOT));
 
             byte[] sha256 = UiccAccessRule.getCertHash(signature, SHA_256);
-            certs.add(IccUtils.bytesToHexString(sha256).toUpperCase());
+            certs.add(IccUtils.bytesToHexString(sha256).toUpperCase(Locale.ROOT));
         }
 
         mInstalledPackageCerts.put(pkg.packageName, certs);
diff --git a/src/java/com/android/internal/telephony/CarrierResolver.java b/src/java/com/android/internal/telephony/CarrierResolver.java
index c80251e..8a9b3e3 100644
--- a/src/java/com/android/internal/telephony/CarrierResolver.java
+++ b/src/java/com/android/internal/telephony/CarrierResolver.java
@@ -55,6 +55,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Locale;
 
 /**
  * CarrierResolver identifies the subscription carrier and returns a canonical carrier Id
@@ -192,14 +193,14 @@
     }
 
     /**
-     * This is triggered from SubscriptionInfoUpdater after sim state change.
+     * This is triggered from UiccController after sim state change.
      * The sequence of sim loading would be
      *  1. OnSubscriptionsChangedListener
      *  2. ACTION_SIM_STATE_CHANGED/ACTION_SIM_CARD_STATE_CHANGED
      *  /ACTION_SIM_APPLICATION_STATE_CHANGED
      *  3. ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED
      *
-     *  For SIM refresh either reset or init refresh type, SubscriptionInfoUpdater will re-trigger
+     *  For SIM refresh either reset or init refresh type, UiccController will re-trigger
      *  carrier identification with sim loaded state. Framework today silently handle single file
      *  refresh type.
      *  TODO: check fileId from single file refresh, if the refresh file is IMSI, gid1 or other
@@ -548,12 +549,7 @@
         // subscriptioninfo db to make sure we have correct carrier id set.
         if (SubscriptionManager.isValidSubscriptionId(mPhone.getSubId()) && !isSimOverride) {
             // only persist carrier id to simInfo db when subId is valid.
-            if (mPhone.isSubscriptionManagerServiceEnabled()) {
-                SubscriptionManagerService.getInstance().setCarrierId(mPhone.getSubId(),
-                        mCarrierId);
-            } else {
-                SubscriptionController.getInstance().setCarrierId(mCarrierId, mPhone.getSubId());
-            }
+            SubscriptionManagerService.getInstance().setCarrierId(mPhone.getSubId(), mCarrierId);
         }
     }
 
@@ -757,7 +753,8 @@
         // Ideally we should do full string match. However due to SIM manufacture issues
         // gid from some SIM might has garbage tail.
         private boolean gidMatch(String gidFromSim, String gid) {
-            return (gidFromSim != null) && gidFromSim.toLowerCase().startsWith(gid.toLowerCase());
+            return (gidFromSim != null) && gidFromSim.toLowerCase(Locale.ROOT)
+                    .startsWith(gid.toLowerCase(Locale.ROOT));
         }
 
         private boolean carrierPrivilegeRulesMatch(List<String> certsFromSubscription,
diff --git a/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java b/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
index bb5b6c0..6b44998 100644
--- a/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
@@ -383,6 +383,7 @@
         Notification.Builder builder = getNotificationBuilder(notificationType);
         // set some common attributes
         builder.setWhen(System.currentTimeMillis())
+                .setShowWhen(true)
                 .setAutoCancel(true)
                 .setSmallIcon(com.android.internal.R.drawable.stat_sys_warning)
                 .setColor(context.getResources().getColor(
diff --git a/src/java/com/android/internal/telephony/CellBroadcastConfigTracker.java b/src/java/com/android/internal/telephony/CellBroadcastConfigTracker.java
new file mode 100644
index 0000000..82d4409
--- /dev/null
+++ b/src/java/com/android/internal/telephony/CellBroadcastConfigTracker.java
@@ -0,0 +1,534 @@
+/*
+ * Copyright 2022 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.os.AsyncResult;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.Message;
+import android.telephony.CellBroadcastIdRange;
+import android.telephony.SmsCbMessage;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
+import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Consumer;
+
+/**
+ * This class is to track the state to set cell broadcast config
+ */
+
+public final class CellBroadcastConfigTracker extends StateMachine {
+    private static final boolean DBG = Build.IS_DEBUGGABLE;
+
+    private static final int EVENT_REQUEST = 1;
+    private static final int EVENT_CONFIGURATION_DONE = 2;
+    private static final int EVENT_ACTIVATION_DONE = 3;
+    private static final int EVENT_RADIO_OFF = 4;
+    private static final int EVENT_SUBSCRIPTION_CHANGED = 5;
+
+    private static final int SMS_CB_CODE_SCHEME_MIN = 0;
+    private static final int SMS_CB_CODE_SCHEME_MAX = 255;
+
+    // Cache of current cell broadcast id ranges of 3gpp
+    private List<CellBroadcastIdRange> mCbRanges3gpp = new CopyOnWriteArrayList<>();
+    // Cache of current cell broadcast id ranges of 3gpp2
+    private List<CellBroadcastIdRange> mCbRanges3gpp2 = new CopyOnWriteArrayList<>();
+    private Phone mPhone;
+    @VisibleForTesting
+    public int mSubId;
+    @VisibleForTesting
+    public final SubscriptionManager.OnSubscriptionsChangedListener mSubChangedListener =
+            new SubscriptionManager.OnSubscriptionsChangedListener() {
+                @Override
+                public void onSubscriptionsChanged() {
+                    sendMessage(EVENT_SUBSCRIPTION_CHANGED);
+                }
+            };
+
+    /**
+     * The class is to present the request to set cell broadcast id ranges
+     */
+    private static class Request {
+        private final List<CellBroadcastIdRange> mCbRangesRequest3gpp =
+                new CopyOnWriteArrayList<>();
+        private final List<CellBroadcastIdRange> mCbRangesRequest3gpp2 =
+                new CopyOnWriteArrayList<>();
+        Consumer<Integer> mCallback;
+
+        Request(@NonNull List<CellBroadcastIdRange> ranges, @NonNull Consumer<Integer> callback) {
+            ranges.forEach(r -> {
+                if (r.getType() == SmsCbMessage.MESSAGE_FORMAT_3GPP) {
+                    mCbRangesRequest3gpp.add(r);
+                } else {
+                    mCbRangesRequest3gpp2.add(r);
+                }
+            });
+            mCallback = callback;
+        }
+
+        List<CellBroadcastIdRange> get3gppRanges() {
+            return mCbRangesRequest3gpp;
+        }
+
+        List<CellBroadcastIdRange> get3gpp2Ranges() {
+            return mCbRangesRequest3gpp2;
+        }
+
+        Consumer<Integer> getCallback() {
+            return mCallback;
+        }
+
+        @Override
+        public String toString() {
+            return "Request[mCbRangesRequest3gpp = " + mCbRangesRequest3gpp + ", "
+                    + "mCbRangesRequest3gpp2 = " + mCbRangesRequest3gpp2 + ", "
+                    + "mCallback = " + mCallback + "]";
+        }
+    }
+
+    /**
+     * The default state.
+     */
+    private class DefaultState extends State {
+        @Override
+        public void enter() {
+            mPhone.registerForRadioOffOrNotAvailable(getHandler(), EVENT_RADIO_OFF, null);
+            mPhone.getContext().getSystemService(SubscriptionManager.class)
+                    .addOnSubscriptionsChangedListener(new HandlerExecutor(getHandler()),
+                            mSubChangedListener);
+        }
+
+        @Override
+        public void exit() {
+            mPhone.unregisterForRadioOffOrNotAvailable(getHandler());
+            mPhone.getContext().getSystemService(SubscriptionManager.class)
+                    .removeOnSubscriptionsChangedListener(mSubChangedListener);
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            boolean retVal = HANDLED;
+            if (DBG) {
+                logd("DefaultState message:" + msg.what);
+            }
+            switch (msg.what) {
+                case EVENT_RADIO_OFF:
+                    resetConfig();
+                    break;
+                case EVENT_SUBSCRIPTION_CHANGED:
+                    int subId = mPhone.getSubId();
+                    if (mSubId != subId) {
+                        log("SubId changed from " + mSubId + " to " + subId);
+                        mSubId = subId;
+                        resetConfig();
+                    }
+                    break;
+                default:
+                    log("unexpected message!");
+                    break;
+
+            }
+
+            return retVal;
+        }
+    }
+
+    private DefaultState mDefaultState = new DefaultState();
+
+    /*
+     * The idle state which does not have ongoing radio request.
+     */
+    private class IdleState extends State {
+        @Override
+        public boolean processMessage(Message msg) {
+            boolean retVal = NOT_HANDLED;
+            if (DBG) {
+                logd("IdleState message:" + msg.what);
+            }
+            switch (msg.what) {
+                case EVENT_REQUEST:
+                    Request request = (Request) msg.obj;
+                    if (DBG) {
+                        logd("IdleState handle EVENT_REQUEST with request:" + request);
+                    }
+                    if (!mCbRanges3gpp.equals(request.get3gppRanges())) {
+                        // set gsm config if the config is changed
+                        setGsmConfig(request.get3gppRanges(), request);
+                        transitionTo(mGsmConfiguringState);
+                    } else if (!mCbRanges3gpp2.equals(request.get3gpp2Ranges())) {
+                        // set cdma config directly if no gsm config change but cdma config is
+                        // changed
+                        setCdmaConfig(request.get3gpp2Ranges(), request);
+                        transitionTo(mCdmaConfiguringState);
+                    } else {
+                        logd("Do nothing as the requested ranges are same as now");
+                        request.getCallback().accept(
+                                TelephonyManager.CELL_BROADCAST_RESULT_SUCCESS);
+                    }
+                    retVal = HANDLED;
+                    break;
+                default:
+                    break;
+            }
+            return retVal;
+        }
+    }
+    private IdleState mIdleState = new IdleState();
+
+    /*
+     * The state waiting for the result to set gsm config.
+     */
+    private class GsmConfiguringState extends State {
+        @Override
+        public boolean processMessage(Message msg) {
+            boolean retVal = NOT_HANDLED;
+            if (DBG) {
+                logd("GsmConfiguringState message:" + msg.what);
+            }
+            switch (msg.what) {
+                case EVENT_REQUEST:
+                    deferMessage(msg);
+                    retVal = HANDLED;
+                    break;
+                case EVENT_CONFIGURATION_DONE:
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    Request request = (Request) ar.userObj;
+                    if (DBG) {
+                        logd("GsmConfiguringState handle EVENT_CONFIGURATION_DONE with request:"
+                                + request);
+                    }
+                    if (ar.exception == null) {
+                        // set gsm activation and transit to gsm activating state
+                        setActivation(SmsCbMessage.MESSAGE_FORMAT_3GPP,
+                                !request.get3gppRanges().isEmpty(), request);
+                        transitionTo(mGsmActivatingState);
+                    } else {
+                        logd("Failed to set gsm config");
+                        request.getCallback().accept(
+                                TelephonyManager.CELL_BROADCAST_RESULT_FAIL_CONFIG);
+                        // transit to idle state on the failure case
+                        transitionTo(mIdleState);
+                    }
+                    retVal = HANDLED;
+                    break;
+                default:
+                    break;
+            }
+            return retVal;
+        }
+    }
+    private GsmConfiguringState mGsmConfiguringState = new GsmConfiguringState();
+
+    /*
+     * The state waiting for the result to set gsm activation.
+     */
+    private class GsmActivatingState extends State {
+        @Override
+        public boolean processMessage(Message msg) {
+            boolean retVal = NOT_HANDLED;
+            if (DBG) {
+                logd("GsmActivatingState message:" + msg.what);
+            }
+            switch (msg.what) {
+                case EVENT_REQUEST:
+                    deferMessage(msg);
+                    retVal = HANDLED;
+                    break;
+                case EVENT_ACTIVATION_DONE:
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    Request request = (Request) ar.userObj;
+                    if (DBG) {
+                        logd("GsmActivatingState handle EVENT_ACTIVATION_DONE with request:"
+                                + request);
+                    }
+                    if (ar.exception == null) {
+                        mCbRanges3gpp = request.get3gppRanges();
+                        if (!mCbRanges3gpp2.equals(request.get3gpp2Ranges())) {
+                            // set cdma config and transit to cdma configuring state if the config
+                            // is changed.
+                            setCdmaConfig(request.get3gpp2Ranges(), request);
+                            transitionTo(mCdmaConfiguringState);
+                        } else {
+                            logd("Done as no need to update ranges for 3gpp2");
+                            request.getCallback().accept(
+                                    TelephonyManager.CELL_BROADCAST_RESULT_SUCCESS);
+                            // transit to idle state if there is no cdma config change
+                            transitionTo(mIdleState);
+                        }
+                    } else {
+                        logd("Failed to set gsm activation");
+                        request.getCallback().accept(
+                                TelephonyManager.CELL_BROADCAST_RESULT_FAIL_ACTIVATION);
+                        // transit to idle state on the failure case
+                        transitionTo(mIdleState);
+                    }
+                    retVal = HANDLED;
+                    break;
+                default:
+                    break;
+            }
+            return retVal;
+        }
+    }
+    private GsmActivatingState mGsmActivatingState = new GsmActivatingState();
+
+    /*
+     * The state waiting for the result to set cdma config.
+     */
+    private class CdmaConfiguringState extends State {
+        @Override
+        public boolean processMessage(Message msg) {
+            boolean retVal = NOT_HANDLED;
+            if (DBG) {
+                logd("CdmaConfiguringState message:" + msg.what);
+            }
+            switch (msg.what) {
+                case EVENT_REQUEST:
+                    deferMessage(msg);
+                    retVal = HANDLED;
+                    break;
+                case EVENT_CONFIGURATION_DONE:
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    Request request = (Request) ar.userObj;
+                    if (DBG) {
+                        logd("CdmaConfiguringState handle EVENT_ACTIVATION_DONE with request:"
+                                + request);
+                    }
+                    if (ar.exception == null) {
+                        // set cdma activation and transit to cdma activating state
+                        setActivation(SmsCbMessage.MESSAGE_FORMAT_3GPP2,
+                                !request.get3gpp2Ranges().isEmpty(), request);
+                        transitionTo(mCdmaActivatingState);
+                    } else {
+                        logd("Failed to set cdma config");
+                        request.getCallback().accept(
+                                TelephonyManager.CELL_BROADCAST_RESULT_FAIL_CONFIG);
+                        // transit to idle state on the failure case
+                        transitionTo(mIdleState);
+                    }
+                    retVal = HANDLED;
+                    break;
+                default:
+                    break;
+            }
+            return retVal;
+        }
+    }
+    private CdmaConfiguringState mCdmaConfiguringState = new CdmaConfiguringState();
+
+    /*
+     * The state waiting for the result to set cdma activation.
+     */
+    private class CdmaActivatingState extends State {
+        @Override
+        public boolean processMessage(Message msg) {
+            boolean retVal = NOT_HANDLED;
+            if (DBG) {
+                logd("CdmaActivatingState message:" + msg.what);
+            }
+            switch (msg.what) {
+                case EVENT_REQUEST:
+                    deferMessage(msg);
+                    retVal = HANDLED;
+                    break;
+                case EVENT_ACTIVATION_DONE:
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    Request request = (Request) ar.userObj;
+                    if (DBG) {
+                        logd("CdmaActivatingState handle EVENT_ACTIVATION_DONE with request:"
+                                + request);
+                    }
+                    if (ar.exception == null) {
+                        mCbRanges3gpp2 = request.get3gpp2Ranges();
+                        request.getCallback().accept(
+                                    TelephonyManager.CELL_BROADCAST_RESULT_SUCCESS);
+                    } else {
+                        logd("Failed to set cdma activation");
+                        request.getCallback().accept(
+                                TelephonyManager.CELL_BROADCAST_RESULT_FAIL_ACTIVATION);
+                    }
+                    // transit to idle state anyway
+                    transitionTo(mIdleState);
+                    retVal = HANDLED;
+                    break;
+                default:
+                    break;
+            }
+            return retVal;
+        }
+    }
+    private CdmaActivatingState mCdmaActivatingState = new CdmaActivatingState();
+
+    private CellBroadcastConfigTracker(Phone phone) {
+        super("CellBroadcastConfigTracker-" + phone.getPhoneId());
+        init(phone);
+    }
+
+    private CellBroadcastConfigTracker(Phone phone, Handler handler) {
+        super("CellBroadcastConfigTracker-" + phone.getPhoneId(), handler);
+        init(phone);
+    }
+
+    private void init(Phone phone) {
+        logd("init");
+        mPhone = phone;
+        mSubId = mPhone.getSubId();
+
+        addState(mDefaultState);
+        addState(mIdleState, mDefaultState);
+        addState(mGsmConfiguringState, mDefaultState);
+        addState(mGsmActivatingState, mDefaultState);
+        addState(mCdmaConfiguringState, mDefaultState);
+        addState(mCdmaActivatingState, mDefaultState);
+        setInitialState(mIdleState);
+    }
+
+    /**
+     * create a CellBroadcastConfigTracker instance for the phone
+     */
+    public static CellBroadcastConfigTracker make(Phone phone, Handler handler,
+            boolean shouldStart) {
+        CellBroadcastConfigTracker tracker = handler == null
+                ? new CellBroadcastConfigTracker(phone)
+                : new CellBroadcastConfigTracker(phone, handler);
+        if (shouldStart) {
+            tracker.start();
+        }
+        return tracker;
+    }
+
+    /**
+     * Return current cell broadcast ranges.
+     */
+    @NonNull public List<CellBroadcastIdRange> getCellBroadcastIdRanges() {
+        List<CellBroadcastIdRange> ranges = new ArrayList<>();
+        ranges.addAll(mCbRanges3gpp);
+        ranges.addAll(mCbRanges3gpp2);
+        return ranges;
+    }
+
+    /**
+     * Set reception of cell broadcast messages with the list of the given ranges.
+     */
+    public void setCellBroadcastIdRanges(
+            @NonNull List<CellBroadcastIdRange> ranges, @NonNull Consumer<Integer> callback) {
+        if (DBG) {
+            logd("setCellBroadcastIdRanges with ranges:" + ranges);
+        }
+        ranges = mergeRangesAsNeeded(ranges);
+        sendMessage(EVENT_REQUEST, new Request(ranges, callback));
+    }
+
+    /**
+     * Merge the overlapped CellBroadcastIdRanges in the list as needed
+     * @param ranges the list of CellBroadcastIdRanges
+     * @return the list of CellBroadcastIdRanges without overlapping
+     *
+     * @throws IllegalArgumentException if there is conflict of the ranges. For instance,
+     * the channel is enabled in some range, but disable in others.
+     */
+    @VisibleForTesting
+    public static @NonNull List<CellBroadcastIdRange> mergeRangesAsNeeded(
+            @NonNull List<CellBroadcastIdRange> ranges) throws IllegalArgumentException {
+        ranges.sort((r1, r2) -> r1.getType() != r2.getType() ? r1.getType() - r2.getType()
+                : (r1.getStartId() != r2.getStartId() ? r1.getStartId() - r2.getStartId()
+                : r2.getEndId() - r1.getEndId()));
+        final List<CellBroadcastIdRange> newRanges = new ArrayList<>();
+        ranges.forEach(r -> {
+            if (newRanges.isEmpty() || newRanges.get(newRanges.size() - 1).getType() != r.getType()
+                    || newRanges.get(newRanges.size() - 1).getEndId() + 1 < r.getStartId()
+                    || (newRanges.get(newRanges.size() - 1).getEndId() + 1 == r.getStartId()
+                    && newRanges.get(newRanges.size() - 1).isEnabled() != r.isEnabled())) {
+                newRanges.add(new CellBroadcastIdRange(r.getStartId(), r.getEndId(),
+                        r.getType(), r.isEnabled()));
+            } else {
+                if (newRanges.get(newRanges.size() - 1).isEnabled() != r.isEnabled()) {
+                    throw new IllegalArgumentException("range conflict " + r);
+                }
+                if (r.getEndId() > newRanges.get(newRanges.size() - 1).getEndId()) {
+                    CellBroadcastIdRange range = newRanges.get(newRanges.size() - 1);
+                    newRanges.set(newRanges.size() - 1, new CellBroadcastIdRange(
+                            range.getStartId(), r.getEndId(), range.getType(), range.isEnabled()));
+                }
+            }
+        });
+        return newRanges;
+    }
+
+    private void resetConfig() {
+        mCbRanges3gpp.clear();
+        mCbRanges3gpp2.clear();
+    }
+
+    private void setGsmConfig(List<CellBroadcastIdRange> ranges, Request request) {
+        if (DBG) {
+            logd("setGsmConfig with " + ranges);
+        }
+
+        SmsBroadcastConfigInfo[] configs = new SmsBroadcastConfigInfo[ranges.size()];
+        for (int i = 0; i < configs.length; i++) {
+            CellBroadcastIdRange r = ranges.get(i);
+            configs[i] = new SmsBroadcastConfigInfo(r.getStartId(), r.getEndId(),
+                    SMS_CB_CODE_SCHEME_MIN, SMS_CB_CODE_SCHEME_MAX, r.isEnabled());
+        }
+
+        Message response = obtainMessage(EVENT_CONFIGURATION_DONE, request);
+        mPhone.mCi.setGsmBroadcastConfig(configs, response);
+    }
+
+    private void setCdmaConfig(List<CellBroadcastIdRange> ranges, Request request) {
+        if (DBG) {
+            logd("setCdmaConfig with " + ranges);
+        }
+
+        CdmaSmsBroadcastConfigInfo[] configs =
+                new CdmaSmsBroadcastConfigInfo[ranges.size()];
+        for (int i = 0; i < configs.length; i++) {
+            CellBroadcastIdRange r = ranges.get(i);
+            configs[i] = new CdmaSmsBroadcastConfigInfo(
+                    r.getStartId(), r.getEndId(), 1, r.isEnabled());
+        }
+
+        Message response = obtainMessage(EVENT_CONFIGURATION_DONE, request);
+        mPhone.mCi.setCdmaBroadcastConfig(configs, response);
+    }
+
+    private void setActivation(int type, boolean activate, Request request) {
+        if (DBG) {
+            logd("setActivation(" + type + "." + activate + ')');
+        }
+
+        Message response = obtainMessage(EVENT_ACTIVATION_DONE, request);
+
+        if (type == SmsCbMessage.MESSAGE_FORMAT_3GPP) {
+            mPhone.mCi.setGsmBroadcastActivation(activate, response);
+        } else if (type == SmsCbMessage.MESSAGE_FORMAT_3GPP2) {
+            mPhone.mCi.setCdmaBroadcastActivation(activate, response);
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/CellularNetworkService.java b/src/java/com/android/internal/telephony/CellularNetworkService.java
index 4253905..9cbd7a6 100644
--- a/src/java/com/android/internal/telephony/CellularNetworkService.java
+++ b/src/java/com/android/internal/telephony/CellularNetworkService.java
@@ -18,9 +18,9 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.hardware.radio.V1_0.RegState;
 import android.hardware.radio.V1_4.DataRegStateResult.VopsInfo.hidl_discriminator;
 import android.hardware.radio.V1_6.RegStateResult.AccessTechnologySpecificInfo;
+import android.hardware.radio.network.RegState;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Looper;
@@ -35,6 +35,7 @@
 import android.telephony.CellIdentityNr;
 import android.telephony.CellIdentityTdscdma;
 import android.telephony.CellIdentityWcdma;
+import android.telephony.DataSpecificRegistrationInfo;
 import android.telephony.LteVopsSupportInfo;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.NetworkService;
@@ -190,6 +191,8 @@
                     return NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN;
                 case RegState.REG_ROAMING:
                     return NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING;
+                case RegState.REG_EM:
+                    return NetworkRegistrationInfo.REGISTRATION_STATE_EMERGENCY;
                 default:
                     return NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING;
             }
@@ -201,6 +204,7 @@
                 case RegState.NOT_REG_MT_SEARCHING_OP_EM:
                 case RegState.REG_DENIED_EM:
                 case RegState.UNKNOWN_EM:
+                case RegState.REG_EM:
                     return true;
                 case RegState.NOT_REG_MT_NOT_SEARCHING_OP:
                 case RegState.REG_HOME:
@@ -504,7 +508,7 @@
                     && reasonForDenial
                     == android.hardware.radio.network.RegistrationFailCause.NONE) {
                 AnomalyReporter.reportAnomaly(
-                        UUID.fromString("62ed270f-e139-418a-a427-8bcc1bca8f20"),
+                        UUID.fromString("62ed270f-e139-418a-a427-8bcc1bca8f21"),
                             "RIL Missing Reg Fail Reason", mPhone.getCarrierId());
             }
 
@@ -522,6 +526,8 @@
             boolean isNrAvailable = false;
             boolean isDcNrRestricted = false;
             VopsSupportInfo vopsInfo = null;
+            int lteAttachResultType = 0;
+            int lteAttachExtraInfo = 0;
 
             android.hardware.radio.network.AccessTechnologySpecificInfo info =
                     regResult.accessTechnologySpecificInfo;
@@ -540,6 +546,8 @@
                     vopsInfo = convertHalLteVopsSupportInfo(
                             info.getEutranInfo().lteVopsInfo.isVopsSupported,
                             info.getEutranInfo().lteVopsInfo.isEmcBearerSupported);
+                    lteAttachResultType = info.getEutranInfo().lteAttachResultType;
+                    lteAttachExtraInfo = info.getEutranInfo().extraInfo;
                     break;
                 case android.hardware.radio.network.AccessTechnologySpecificInfo.ngranNrVopsInfo:
                     vopsInfo = new NrVopsSupportInfo(info.getNgranNrVopsInfo().vopsSupported,
@@ -565,10 +573,26 @@
                     loge("Unknown domain passed to CellularNetworkService= " + domain);
                     // fall through
                 case NetworkRegistrationInfo.DOMAIN_PS:
-                    return new NetworkRegistrationInfo(domain, transportType, regState, networkType,
-                            reasonForDenial, isEmergencyOnly, availableServices, cellIdentity,
-                            rplmn, MAX_DATA_CALLS, isDcNrRestricted, isNrAvailable, isEndcAvailable,
-                            vopsInfo);
+                    return new NetworkRegistrationInfo.Builder()
+                        .setDomain(domain)
+                        .setTransportType(transportType)
+                        .setRegistrationState(regState)
+                        .setAccessNetworkTechnology(networkType)
+                        .setRejectCause(reasonForDenial)
+                        .setEmergencyOnly(isEmergencyOnly)
+                        .setAvailableServices(availableServices)
+                        .setCellIdentity(cellIdentity)
+                        .setRegisteredPlmn(rplmn)
+                        .setDataSpecificInfo(
+                                new DataSpecificRegistrationInfo.Builder(MAX_DATA_CALLS)
+                                     .setDcNrRestricted(isDcNrRestricted)
+                                     .setNrAvailable(isNrAvailable)
+                                     .setEnDcAvailable(isEndcAvailable)
+                                     .setVopsSupportInfo(vopsInfo)
+                                     .setLteAttachResultType(lteAttachResultType)
+                                     .setLteAttachExtraInfo(lteAttachExtraInfo)
+                                     .build())
+                        .build();
             }
         }
 
@@ -594,7 +618,7 @@
                     && reasonForDenial
                     == android.hardware.radio.network.RegistrationFailCause.NONE) {
                 AnomalyReporter.reportAnomaly(
-                        UUID.fromString("62ed270f-e139-418a-a427-8bcc1bca8f20"),
+                        UUID.fromString("62ed270f-e139-418a-a427-8bcc1bca8f21"),
                             "RIL Missing Reg Fail Reason", mPhone.getCarrierId());
             }
 
diff --git a/src/java/com/android/internal/telephony/CommandException.java b/src/java/com/android/internal/telephony/CommandException.java
index 72bb6a3..e068c1c 100644
--- a/src/java/com/android/internal/telephony/CommandException.java
+++ b/src/java/com/android/internal/telephony/CommandException.java
@@ -341,7 +341,6 @@
                 return new CommandException(Error.RF_HARDWARE_ISSUE);
             case RILConstants.NO_RF_CALIBRATION_INFO:
                 return new CommandException(Error.NO_RF_CALIBRATION_INFO);
-
             default:
                 Rlog.e("GSM", "Unrecognized RIL errno " + ril_errno);
                 return new CommandException(Error.INVALID_RESPONSE);
diff --git a/src/java/com/android/internal/telephony/CommandsInterface.java b/src/java/com/android/internal/telephony/CommandsInterface.java
index 27cedfe..971e051 100644
--- a/src/java/com/android/internal/telephony/CommandsInterface.java
+++ b/src/java/com/android/internal/telephony/CommandsInterface.java
@@ -25,22 +25,31 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.WorkSource;
+import android.telephony.AccessNetworkConstants;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
+import android.telephony.BarringInfo;
 import android.telephony.CarrierRestrictionRules;
 import android.telephony.ClientRequestStats;
+import android.telephony.DomainSelectionService;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.NetworkScanRequest;
 import android.telephony.RadioAccessSpecifier;
 import android.telephony.SignalThresholdInfo;
 import android.telephony.TelephonyManager;
+import android.telephony.TelephonyManager.HalService;
 import android.telephony.data.DataCallResponse;
 import android.telephony.data.DataProfile;
 import android.telephony.data.NetworkSliceInfo;
 import android.telephony.data.TrafficDescriptor;
 import android.telephony.emergency.EmergencyNumber;
+import android.telephony.ims.RegistrationManager;
+import android.telephony.ims.feature.MmTelFeature;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
 
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
+import com.android.internal.telephony.emergency.EmergencyConstants;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
+import com.android.internal.telephony.imsphone.ImsCallInfo;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.PersoSubState;
 import com.android.internal.telephony.uicc.IccCardStatus;
 import com.android.internal.telephony.uicc.SimPhonebookRecord;
@@ -124,6 +133,15 @@
     static final int CDMA_SMS_FAIL_CAUSE_OTHER_TERMINAL_PROBLEM     = 39;
     static final int CDMA_SMS_FAIL_CAUSE_ENCODING_PROBLEM           = 96;
 
+    /** IMS voice capability */
+    int IMS_MMTEL_CAPABILITY_VOICE = 1 << 0;
+    /** IMS video capability */
+    int IMS_MMTEL_CAPABILITY_VIDEO = 1 << 1;
+    /** IMS SMS capability */
+    int IMS_MMTEL_CAPABILITY_SMS = 1 << 2;
+    /** IMS RCS capabilities */
+    int IMS_RCS_CAPABILITIES = 1 << 3;
+
     //***** Methods
 
     /**
@@ -1773,6 +1791,17 @@
     public void getDeviceIdentity(Message response);
 
     /**
+     * Request the device IMEI / IMEI type / IMEISV
+     * "response" is ImeiInfo object that contains
+     *  [0] ImeiType Indicates whether IMEI is of primary or secondary type
+     *  [1] IMEI if GSM subscription is available
+     *  [2] IMEISV if GSM subscription is available
+     *
+     * @param response Message
+     */
+    public void getImei(Message response);
+
+    /**
      * Request the device MDN / H_SID / H_NID / MIN.
      * "response" is const char **
      *   [0] is MDN if CDMA subscription is available
@@ -2086,10 +2115,15 @@
      *
      * Input parameters equivalent to TS 27.007 AT+CCHC command.
      *
+     * Per spec SGP.22 V3.0, ES10 commands needs to be sent over command port of MEP-A. In order
+     * to close proper logical channel, should pass information about whether the logical channel
+     * was opened for sending ES10 commands or not.
+     *
      * @param channel Channel id. Id of the channel to be closed.
+     * @param isEs10  Whether the logical channel is opened to perform ES10 operations.
      * @param response Callback message.
      */
-    public void iccCloseLogicalChannel(int channel, Message response);
+    public void iccCloseLogicalChannel(int channel, boolean isEs10, Message response);
 
     /**
      * Exchange APDUs with the SIM on a logical channel.
@@ -2105,11 +2139,12 @@
      * @param p3 P3 value of the APDU command. If p3 is negative a 4 byte APDU
      *            is sent to the SIM.
      * @param data Data to be sent with the APDU.
+     * @param isEs10Command whether APDU command is an ES10 command or a regular APDU
      * @param response Callback message. response.obj.userObj will be
      *            an IccIoResult on success.
      */
-    public void iccTransmitApduLogicalChannel(int channel, int cla, int instruction,
-            int p1, int p2, int p3, String data, Message response);
+    void iccTransmitApduLogicalChannel(int channel, int cla, int instruction,
+            int p1, int p2, int p3, String data, boolean isEs10Command, Message response);
 
     /**
      * Exchange APDUs with the SIM on a basic channel.
@@ -2186,11 +2221,21 @@
 
     /**
      * @return the radio hal version
+     * @deprecated use {@link #getHalVersion(int)}
      */
+    @Deprecated
     default HalVersion getHalVersion() {
         return HalVersion.UNKNOWN;
     }
 
+    /**
+     * @param service indicate the service id to query.
+     * @return the hal version of a specific service
+     */
+    default HalVersion getHalVersion(@HalService int service) {
+        return HalVersion.UNKNOWN;
+    }
+
    /**
      * Sets user selected subscription at Modem.
      *
@@ -2619,6 +2664,15 @@
     default void getBarringInfo(Message result) {};
 
     /**
+     * Returns the last barring information received.
+     *
+     * @return the last barring information.
+     */
+    default @Nullable BarringInfo getLastBarringInfo() {
+        return null;
+    };
+
+    /**
      * Allocates a pdu session id
      *
      * AsyncResult.result is the allocated pdu session id
@@ -2747,6 +2801,54 @@
      public void unregisterForSimPhonebookRecordsReceived(Handler h);
 
     /**
+     * Registers for notifications of connection setup failure.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    default void registerForConnectionSetupFailure(Handler h, int what, Object obj) {}
+
+    /**
+     * Unregisters for notifications of connection setup failure.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    default void unregisterForConnectionSetupFailure(Handler h) {}
+
+    /**
+     * Registers for notifications when ANBR is received form the network.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    default void registerForNotifyAnbr(Handler h, int what, Object obj) {}
+
+    /**
+     * Unregisters for notifications when ANBR is received form the network.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    default void unregisterForNotifyAnbr(Handler h) {}
+
+    /**
+     * Registers for IMS deregistration trigger from modem.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    default void registerForTriggerImsDeregistration(Handler h, int what, Object obj) {}
+
+    /**
+     * Unregisters for IMS deregistration trigger from modem.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    default void unregisterForTriggerImsDeregistration(Handler h) {}
+
+    /**
      * Set the UE's usage setting.
      *
      * @param result Callback message containing the success or failure status.
@@ -2761,4 +2863,417 @@
      * @param result Callback message containing the usage setting (or a failure status).
      */
     default void getUsageSetting(Message result) {}
+
+    /**
+     * Sets the emergency mode.
+     *
+     * @param emcMode Defines the radio emergency mode type.
+     * @param result Callback message containing the success or failure status.
+     */
+    default void setEmergencyMode(@EmergencyConstants.EmergencyMode int emcMode,
+            @Nullable Message result) {}
+
+    /**
+     * Triggers an emergency network scan.
+     *
+     * @param accessNetwork Contains the list of access network types to be prioritized
+     *        during emergency scan. The 1st entry has the highest priority.
+     * @param scanType Indicates the type of scans to be performed i.e. limited scan,
+     *        full service scan or both.
+     * @param result Callback message containing the success or failure status.
+     */
+    default void triggerEmergencyNetworkScan(
+            @NonNull @AccessNetworkConstants.RadioAccessNetworkType int[] accessNetwork,
+            @DomainSelectionService.EmergencyScanType int scanType, @Nullable Message result) {}
+
+    /**
+     * Cancels ongoing emergency network scan.
+     *
+     * @param resetScan Indicates how the next {@link #triggerEmergencyNetworkScan} should work.
+     *        If {@code true}, then the modem shall start the new scan from the beginning,
+     *        otherwise the modem shall resume from the last search.
+     * @param result Callback message containing the success or failure status.
+     */
+    default void cancelEmergencyNetworkScan(boolean resetScan, @Nullable Message result) {}
+
+    /**
+     * Exits ongoing emergency mode.
+     *
+     * @param result Callback message containing the success or failure status.
+     */
+    default void exitEmergencyMode(@Nullable Message result) {}
+
+    /**
+     * Registers for emergency network scan result.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    default void registerForEmergencyNetworkScan(@NonNull Handler h,
+            int what, @Nullable Object obj) {}
+
+    /**
+     * Unregisters for emergency network scan result.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    default void unregisterForEmergencyNetworkScan(@NonNull Handler h) {}
+
+    /**
+     * Provides a list of SRVCC call information to radio
+     *
+     * @param srvccConnections the list of connections.
+     */
+    default void setSrvccCallInfo(SrvccConnection[] srvccConnections, Message result) {}
+
+    /**
+     * Updates the IMS registration information to the radio.
+     *
+     * @param state The current IMS registration state.
+     * @param imsRadioTech The type of underlying radio access network used.
+     * @param suggestedAction The suggested action for the radio to perform.
+     * @param capabilities IMS capabilities such as VOICE, VIDEO and SMS.
+     */
+    default void updateImsRegistrationInfo(int state,
+            @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech,
+            @RegistrationManager.SuggestedAction int suggestedAction,
+            int capabilities, Message result) {}
+
+    /**
+     * Notifies the NAS and RRC layers of the radio the type of upcoming IMS traffic.
+     *
+     * @param token A nonce to identify the request.
+     * @param trafficType IMS traffic type like registration, voice, video, SMS, emergency, and etc.
+     * @param accessNetworkType The type of underlying radio access network used.
+     * @param trafficDirection Indicates whether traffic is originated by mobile originated or
+     *        mobile terminated use case eg. MO/MT call/SMS etc.
+     */
+    default void startImsTraffic(int token,
+            @MmTelFeature.ImsTrafficType int trafficType,
+            @AccessNetworkConstants.RadioAccessNetworkType int accessNetworkType,
+            @MmTelFeature.ImsTrafficDirection int trafficDirection,
+            Message result) {}
+
+    /**
+     * Notifies IMS traffic has been stopped.
+     *
+     * @param token The token assigned by startImsTraffic.
+     */
+    default void stopImsTraffic(int token, Message result) {}
+
+    /**
+     * Triggers the UE initiated EPS fallback procedure.
+     *
+     * @param reason Specifies the reason for EPS fallback.
+     */
+    default void triggerEpsFallback(int reason, Message result) {}
+
+    /**
+     * Triggers radio to send ANBRQ message to the network.
+     *
+     * @param mediaType Media type is used to identify media stream such as audio or video.
+     * @param direction Direction of this packet stream (e.g. uplink or downlink).
+     * @param bitsPerSecond The bit rate requested by the opponent UE.
+     * @param result Callback message to receive the result.
+     */
+    default void sendAnbrQuery(int mediaType, int direction, int bitsPerSecond, Message result) {}
+
+    /**
+     * Set the UE's ability to accept/reject null ciphered and/or null integrity-protected
+     * connections.
+     *
+     * @param enabled true to allow null ciphered and/or null integrity-protected connections,
+     * false to disallow.
+     * @param result Callback message containing the success or failure status.
+     */
+    default void setNullCipherAndIntegrityEnabled(boolean enabled, Message result) {}
+
+    /**
+     * Check whether null ciphering and/or null integrity-protected connections are allowed.
+     *
+     * @param result Callback message containing the success or failure status.
+     */
+    default void isNullCipherAndIntegrityEnabled(Message result) {}
+
+    /**
+     * Notifies the IMS call status to the modem.
+     *
+     * @param imsCallInfo The list of {@link ImsCallInfo}.
+     * @param result A callback to receive the response.
+     */
+    default void updateImsCallStatus(@NonNull List<ImsCallInfo> imsCallInfo, Message result) {}
+
+    /**
+     * Enables or disables N1 mode (access to 5G core network) in accordance with
+     * 3GPP TS 24.501 4.9.
+     * @param enable {@code true} to enable N1 mode, {@code false} to disable N1 mode.
+     * @param result Callback message to receive the result.
+     */
+    default void setN1ModeEnabled(boolean enable, Message result) {}
+
+    /**
+     * Check whether N1 mode (access to 5G core network) is enabled or not.
+     * @param result Callback message to receive the result.
+     */
+    default void isN1ModeEnabled(Message result) {}
+
+    /**
+     * Get feature capabilities supported by satellite.
+     *
+     * @param result Message that will be sent back to the requester
+     */
+    default void getSatelliteCapabilities(Message result) {}
+
+    /**
+     * Turn satellite modem on/off.
+     *
+     * @param result Message that will be sent back to the requester
+     * @param on {@code true} for turning on.
+     *           {@code false} for turning off.
+     */
+    default void setSatellitePower(Message result, boolean on) {}
+
+    /**
+     * Get satellite modem state.
+     *
+     * @param result Message that will be sent back to the requester
+     */
+    default void getSatellitePowerState(Message result) {}
+
+    /**
+     * Get satellite provision state.
+     *
+     * @param result Message that will be sent back to the requester
+     */
+    default void getSatelliteProvisionState(Message result) {}
+
+    /**
+     * Check whether satellite modem is supported by the device.
+     *
+     * @param result Message that will be sent back to the requester
+     */
+    default void isSatelliteSupported(Message result) {}
+
+    /**
+     * Provision the subscription with a satellite provider. This is needed to register the
+     * subscription if the provider allows dynamic registration.
+     *
+     * @param result Message that will be sent back to the requester.
+     * @param imei IMEI of the SIM associated with the satellite modem.
+     * @param msisdn MSISDN of the SIM associated with the satellite modem.
+     * @param imsi IMSI of the SIM associated with the satellite modem.
+     * @param features List of features to be provisioned.
+     */
+    default void provisionSatelliteService(
+            Message result, String imei, String msisdn, String imsi, int[] features) {}
+
+    /**
+     * Add contacts that are allowed to be used for satellite communication. This is applicable for
+     * incoming messages as well.
+     *
+     * @param result Message that will be sent back to the requester.
+     * @param contacts List of allowed contacts to be added.
+     */
+    default void addAllowedSatelliteContacts(Message result, String[] contacts) {}
+
+    /**
+     * Remove contacts that are allowed to be used for satellite communication. This is applicable
+     * for incoming messages as well.
+     *
+     * @param result Message that will be sent back to the requester.
+     * @param contacts List of allowed contacts to be removed.
+     */
+    default void removeAllowedSatelliteContacts(Message result, String[] contacts) {}
+
+    /**
+     * Send text messages.
+     *
+     * @param result Message that will be sent back to the requester.
+     * @param messages List of messages in text format to be sent.
+     * @param destination The recipient of the message.
+     * @param latitude The current latitude of the device.
+     * @param longitude The current longitude of the device. The location (i.e., latitude and
+     *        longitude) of the device will be filled for emergency messages.
+     */
+    default void sendSatelliteMessages(Message result, String[] messages, String destination,
+            double latitude, double longitude) {}
+
+    /**
+     * Get pending messages.
+     *
+     * @param result Message that will be sent back to the requester.
+     */
+    default void getPendingSatelliteMessages(Message result) {}
+
+    /**
+     * Get current satellite registration mode.
+     *
+     * @param result Message that will be sent back to the requester.
+     */
+    default void getSatelliteMode(Message result) {}
+
+    /**
+     * Set the filter for what type of indication framework want to receive from modem.
+     *
+     * @param result Message that will be sent back to the requester.
+     * @param filterBitmask The filter bitmask identifying what type of indication Telephony
+     *                      framework wants to receive from modem.
+     */
+    default void setSatelliteIndicationFilter(Message result, int filterBitmask) {}
+
+    /**
+     * User started pointing to the satellite. Modem should continue to update the ponting input
+     * as user moves device.
+     *
+     * @param result Message that will be sent back to the requester.
+     */
+    default void startSendingSatellitePointingInfo(Message result) {}
+
+    /**
+     * Stop sending satellite pointing info to the framework.
+     *
+     * @param result Message that will be sent back to the requester.
+     */
+    default void stopSendingSatellitePointingInfo(Message result) {}
+
+    /**
+     * Get max number of characters per text message.
+     *
+     * @param result Message that will be sent back to the requester.
+     */
+    default void getMaxCharactersPerSatelliteTextMessage(Message result) {}
+
+    /**
+     * Get whether satellite communication is allowed for the current location.
+     *
+     * @param result Message that will be sent back to the requester.
+     */
+    default void isSatelliteCommunicationAllowedForCurrentLocation(Message result) {}
+
+    /**
+     * Get the time after which the satellite will be visible.
+     *
+     * @param result Message that will be sent back to the requester.
+     */
+    default void getTimeForNextSatelliteVisibility(Message result) {}
+
+    /**
+     * Registers for pending message count from satellite modem.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    default void registerForPendingSatelliteMessageCount(@NonNull Handler h,
+            int what, @Nullable Object obj) {}
+
+    /**
+     * Unregisters for pending message count from satellite modem.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    default void unregisterForPendingSatelliteMessageCount(@NonNull Handler h) {}
+
+    /**
+     * Registers for new messages from satellite modem.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    default void registerForNewSatelliteMessages(@NonNull Handler h,
+            int what, @Nullable Object obj) {}
+
+    /**
+     * Unregisters for new messages from satellite modem.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    default void unregisterForNewSatelliteMessages(@NonNull Handler h) {}
+
+    /**
+     * Registers for messages transfer complete from satellite modem.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    default void registerForSatelliteMessagesTransferComplete(@NonNull Handler h,
+            int what, @Nullable Object obj) {}
+
+    /**
+     * Unregisters for messages transfer complete from satellite modem.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    default void unregisterForSatelliteMessagesTransferComplete(@NonNull Handler h) {}
+
+    /**
+     * Registers for pointing info changed from satellite modem.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    default void registerForSatellitePointingInfoChanged(@NonNull Handler h,
+            int what, @Nullable Object obj) {}
+
+    /**
+     * Unregisters for pointing info changed from satellite modem.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    default void unregisterForSatellitePointingInfoChanged(@NonNull Handler h) {}
+
+    /**
+     * Registers for mode changed from satellite modem.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    default void registerForSatelliteModeChanged(@NonNull Handler h,
+            int what, @Nullable Object obj) {}
+
+    /**
+     * Unregisters for mode changed from satellite modem.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    default void unregisterForSatelliteModeChanged(@NonNull Handler h) {}
+
+    /**
+     * Registers for radio technology changed from satellite modem.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    default void registerForSatelliteRadioTechnologyChanged(@NonNull Handler h,
+            int what, @Nullable Object obj) {}
+
+    /**
+     * Unregisters for radio technology changed from satellite modem.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    default void unregisterForSatelliteRadioTechnologyChanged(@NonNull Handler h) {}
+
+    /**
+     * Registers for provision state changed from satellite modem.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    default void registerForSatelliteProvisionStateChanged(@NonNull Handler h,
+            int what, @Nullable Object obj) {}
+
+    /**
+     * Unregisters for provision state changed from satellite modem.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    default void unregisterForSatelliteProvisionStateChanged(@NonNull Handler h) {}
 }
diff --git a/src/java/com/android/internal/telephony/Connection.java b/src/java/com/android/internal/telephony/Connection.java
index c60e5df..68fd6ab 100644
--- a/src/java/com/android/internal/telephony/Connection.java
+++ b/src/java/com/android/internal/telephony/Connection.java
@@ -27,6 +27,8 @@
 import android.telephony.ServiceState.RilRadioTechnology;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.RtpHeaderExtension;
+import android.telephony.ims.feature.MmTelFeature;
+import android.telephony.ims.feature.MmTelFeature.ImsAudioHandler;
 import android.util.Log;
 
 import com.android.ims.internal.ConferenceParticipant;
@@ -139,6 +141,13 @@
          * @param extensionData The extension data.
          */
         public void onReceivedRtpHeaderExtensions(@NonNull Set<RtpHeaderExtension> extensionData);
+
+        /**
+         * Indicates that the audio handler for this connection is changed.
+         *
+         * @param imsAudioHandler {@link MmTelFeature#ImsAudioHandler}.
+         */
+        void onAudioModeIsVoipChanged(@ImsAudioHandler int imsAudioHandler);
     }
 
     /**
@@ -194,6 +203,8 @@
         public void onReceivedDtmfDigit(char digit) {}
         @Override
         public void onReceivedRtpHeaderExtensions(@NonNull Set<RtpHeaderExtension> extensionData) {}
+        @Override
+        public void onAudioModeIsVoipChanged(@ImsAudioHandler int imsAudioHandler) {}
     }
 
     public static final int AUDIO_QUALITY_STANDARD = 1;
@@ -328,6 +339,41 @@
     /* Instance Methods */
 
     /**
+     * PhoneFactory Dependencies for testing.
+     */
+    @VisibleForTesting
+    public interface PhoneFactoryProxy {
+        Phone getPhone(int index);
+        Phone getDefaultPhone();
+        Phone[] getPhones();
+    }
+
+    private PhoneFactoryProxy mPhoneFactoryProxy = new PhoneFactoryProxy() {
+        @Override
+        public Phone getPhone(int index) {
+            return PhoneFactory.getPhone(index);
+        }
+
+        @Override
+        public Phone getDefaultPhone() {
+            return PhoneFactory.getDefaultPhone();
+        }
+
+        @Override
+        public Phone[] getPhones() {
+            return PhoneFactory.getPhones();
+        }
+    };
+
+    /**
+     * Overrides PhoneFactory dependencies for testing.
+     */
+    @VisibleForTesting
+    public void setPhoneFactoryProxy(PhoneFactoryProxy proxy) {
+        mPhoneFactoryProxy = proxy;
+    }
+
+    /**
      * @return The telecom internal call ID associated with this connection.  Only to be used for
      * debugging purposes.
      */
@@ -590,14 +636,35 @@
      */
     public void setEmergencyCallInfo(CallTracker ct) {
         if (ct != null) {
-            Phone phone = ct.getPhone();
-            if (phone != null) {
-                EmergencyNumberTracker tracker = phone.getEmergencyNumberTracker();
+            Phone currentPhone = ct.getPhone();
+            if (currentPhone != null) {
+                EmergencyNumberTracker tracker = currentPhone.getEmergencyNumberTracker();
                 if (tracker != null) {
                     EmergencyNumber num = tracker.getEmergencyNumber(mAddress);
+                    Phone[] allPhones = mPhoneFactoryProxy.getPhones();
                     if (num != null) {
                         mIsEmergencyCall = true;
                         mEmergencyNumberInfo = num;
+                    } else if (allPhones.length > 1) {
+                        // If there are multiple active SIMs, check all instances:
+                        boolean found = false;
+                        for (Phone phone : allPhones) {
+                            // If the current iteration was already checked, skip:
+                            if (phone.getPhoneId() == currentPhone.getPhoneId()){
+                                continue;
+                            }
+                            num = phone.getEmergencyNumberTracker()
+                                    .getEmergencyNumber(mAddress);
+                            if (num != null){
+                                found = true;
+                                mIsEmergencyCall = true;
+                                mEmergencyNumberInfo = num;
+                                break;
+                            }
+                        }
+                        if (!found){
+                            Rlog.e(TAG, "setEmergencyCallInfo: emergency number is null");
+                        }
                     } else {
                         Rlog.e(TAG, "setEmergencyCallInfo: emergency number is null");
                     }
@@ -1519,6 +1586,25 @@
     }
 
     /**
+     * Called to report audio mode changed for Voip.
+     * @param imsAudioHandler the received value to handle the audio for this IMS call.
+     */
+    public void onAudioModeIsVoipChanged(@ImsAudioHandler int imsAudioHandler) {
+        Rlog.i(TAG, "onAudioModeIsVoipChanged: conn imsAudioHandler " + imsAudioHandler);
+
+        boolean isVoip = imsAudioHandler == MmTelFeature.AUDIO_HANDLER_ANDROID;
+        if (isVoip == mAudioModeIsVoip) return;
+        mAudioModeIsVoip = isVoip;
+
+        Rlog.i(TAG, "onAudioModeIsVoipChanged: isVoip: " + isVoip
+                + "mAudioModeIsVoip:" + mAudioModeIsVoip);
+
+        for (Listener l : mListeners) {
+            l.onAudioModeIsVoipChanged(imsAudioHandler);
+        }
+    }
+
+    /**
      * Called to report RTP header extensions received from the network.
      * @param extensionData the received extension data.
      */
diff --git a/src/java/com/android/internal/telephony/DataIndication.java b/src/java/com/android/internal/telephony/DataIndication.java
index 3467955..205c4d8 100644
--- a/src/java/com/android/internal/telephony/DataIndication.java
+++ b/src/java/com/android/internal/telephony/DataIndication.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony;
 
+import static android.telephony.TelephonyManager.HAL_SERVICE_DATA;
+
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_DATA_CALL_LIST_CHANGED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_KEEPALIVE_STATUS;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_PCO_DATA;
@@ -53,7 +55,7 @@
      */
     public void dataCallListChanged(int indicationType,
             android.hardware.radio.data.SetupDataCallResult[] dcList) {
-        mRil.processIndication(RIL.DATA_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_DATA, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_DATA_CALL_LIST_CHANGED, dcList);
         ArrayList<DataCallResponse> response = RILUtils.convertHalDataCallResultList(dcList);
@@ -68,7 +70,7 @@
      */
     public void keepaliveStatus(int indicationType,
             android.hardware.radio.data.KeepaliveStatus halStatus) {
-        mRil.processIndication(RIL.DATA_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_DATA, indicationType);
 
         if (mRil.isLogOrTrace()) {
             mRil.unsljLogRet(
@@ -87,7 +89,7 @@
      * @param pco New PcoData
      */
     public void pcoData(int indicationType, android.hardware.radio.data.PcoDataInfo pco) {
-        mRil.processIndication(RIL.DATA_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_DATA, indicationType);
 
         PcoData response = new PcoData(pco.cid, pco.bearerProto, pco.pcoId, pco.contents);
 
@@ -104,7 +106,7 @@
      */
     public void unthrottleApn(int indicationType, android.hardware.radio.data.DataProfileInfo dpi)
             throws RemoteException {
-        mRil.processIndication(RIL.DATA_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_DATA, indicationType);
         DataProfile response = RILUtils.convertToDataProfile(dpi);
 
         if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_UNTHROTTLE_APN, response);
@@ -120,7 +122,7 @@
      */
     public void slicingConfigChanged(int indicationType,
             android.hardware.radio.data.SlicingConfig slicingConfig) throws RemoteException {
-        mRil.processIndication(RIL.DATA_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_DATA, indicationType);
         if (mRil.isLogOrTrace()) {
             mRil.unsljLogRet(RIL_UNSOL_SLICING_CONFIG_CHANGED, slicingConfig);
         }
diff --git a/src/java/com/android/internal/telephony/DataResponse.java b/src/java/com/android/internal/telephony/DataResponse.java
index 7cfe13b..bef1da7 100644
--- a/src/java/com/android/internal/telephony/DataResponse.java
+++ b/src/java/com/android/internal/telephony/DataResponse.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony;
 
+import static android.telephony.TelephonyManager.HAL_SERVICE_DATA;
+
 import android.hardware.radio.RadioError;
 import android.hardware.radio.RadioResponseInfo;
 import android.hardware.radio.data.IRadioDataResponse;
@@ -51,7 +53,7 @@
      * @param id The pdu session id allocated
      */
     public void allocatePduSessionIdResponse(RadioResponseInfo responseInfo, int id) {
-        RILRequest rr = mRil.processResponse(RIL.DATA_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_DATA, responseInfo);
         if (rr != null) {
             if (responseInfo.error == RadioError.NONE) {
                 RadioResponse.sendMessageResponse(rr.mResult, id);
@@ -64,14 +66,14 @@
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void cancelHandoverResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.DATA_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_DATA, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void deactivateDataCallResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.DATA_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_DATA, mRil, responseInfo);
     }
 
     /**
@@ -80,7 +82,7 @@
      */
     public void getDataCallListResponse(RadioResponseInfo responseInfo,
             android.hardware.radio.data.SetupDataCallResult[] dataCallResultList) {
-        RILRequest rr = mRil.processResponse(RIL.DATA_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_DATA, responseInfo);
 
         if (rr != null) {
             ArrayList<DataCallResponse> response =
@@ -98,7 +100,7 @@
      */
     public void getSlicingConfigResponse(RadioResponseInfo responseInfo,
                 android.hardware.radio.data.SlicingConfig slicingConfig) {
-        RILRequest rr = mRil.processResponse(RIL.DATA_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_DATA, responseInfo);
 
         if (rr != null) {
             NetworkSlicingConfig ret = RILUtils.convertHalSlicingConfig(slicingConfig);
@@ -113,35 +115,35 @@
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void releasePduSessionIdResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.DATA_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_DATA, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setDataAllowedResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.DATA_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_DATA, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setDataProfileResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.DATA_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_DATA, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setDataThrottlingResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.DATA_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_DATA, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setInitialAttachApnResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.DATA_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_DATA, mRil, responseInfo);
     }
 
     /**
@@ -150,7 +152,7 @@
      */
     public void setupDataCallResponse(RadioResponseInfo responseInfo,
             android.hardware.radio.data.SetupDataCallResult setupDataCallResult) {
-        RILRequest rr = mRil.processResponse(RIL.DATA_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_DATA, responseInfo);
 
         if (rr != null) {
             DataCallResponse response = RILUtils.convertHalDataCallResult(setupDataCallResult);
@@ -165,7 +167,7 @@
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void startHandoverResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.DATA_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_DATA, mRil, responseInfo);
     }
 
     /**
@@ -175,7 +177,7 @@
     public void startKeepaliveResponse(RadioResponseInfo responseInfo,
             android.hardware.radio.data.KeepaliveStatus keepaliveStatus) {
 
-        RILRequest rr = mRil.processResponse(RIL.DATA_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_DATA, responseInfo);
         if (rr == null) return;
 
         KeepaliveStatus ret = null;
@@ -214,7 +216,7 @@
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void stopKeepaliveResponse(RadioResponseInfo responseInfo) {
-        RILRequest rr = mRil.processResponse(RIL.DATA_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_DATA, responseInfo);
         if (rr == null) return;
 
         try {
diff --git a/src/java/com/android/internal/telephony/DebugService.java b/src/java/com/android/internal/telephony/DebugService.java
index 5cc730c..3341577 100644
--- a/src/java/com/android/internal/telephony/DebugService.java
+++ b/src/java/com/android/internal/telephony/DebugService.java
@@ -52,13 +52,15 @@
                     TelephonyMetrics.getInstance().dump(fd, pw, args);
                     return;
                 case "--saveatoms":
-                    log("Saving atoms..");
-                    PhoneFactory.getMetricsCollector().getAtomsStorage().flushAtoms();
+                    if (Build.IS_DEBUGGABLE) {
+                        log("Saving atoms..");
+                        PhoneFactory.getMetricsCollector().flushAtomsStorage();
+                    }
                     return;
                 case "--clearatoms":
                     if (Build.IS_DEBUGGABLE) {
                         log("Clearing atoms..");
-                        PhoneFactory.getMetricsCollector().getAtomsStorage().clearAtoms();
+                        PhoneFactory.getMetricsCollector().clearAtomsStorage();
                     }
                     return;
             }
diff --git a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
index e4aff4c..e5a5c8f 100644
--- a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.telephony.Annotation;
 import android.telephony.Annotation.RadioPowerState;
 import android.telephony.Annotation.SrvccState;
 import android.telephony.BarringInfo;
@@ -32,9 +33,13 @@
 import android.telephony.ServiceState;
 import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager.DataEnabledReason;
+import android.telephony.TelephonyManager.EmergencyCallbackModeStopReason;
+import android.telephony.TelephonyManager.EmergencyCallbackModeType;
 import android.telephony.TelephonyRegistryManager;
 import android.telephony.emergency.EmergencyNumber;
+import android.telephony.ims.ImsCallSession;
 import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.MediaQualityStatus;
 
 import com.android.telephony.Rlog;
 
@@ -142,15 +147,28 @@
         mTelephonyRegistryMgr.notifyCellInfoChanged(subId, cellInfo);
     }
 
-    public void notifyPreciseCallState(Phone sender) {
+    /**
+     * Notify precise call state of foreground, background and ringing call states.
+     *
+     * @param imsCallIds Array of IMS call session ID{@link ImsCallSession#getCallId} for
+     *                   ringing, foreground & background calls.
+     * @param imsCallServiceTypes Array of IMS call service type for ringing, foreground &
+     *                        background calls.
+     * @param imsCallTypes Array of IMS call type for ringing, foreground & background calls.
+     */
+    public void notifyPreciseCallState(Phone sender, String[] imsCallIds,
+            @Annotation.ImsCallServiceType int[] imsCallServiceTypes,
+            @Annotation.ImsCallType int[] imsCallTypes) {
         Call ringingCall = sender.getRingingCall();
         Call foregroundCall = sender.getForegroundCall();
         Call backgroundCall = sender.getBackgroundCall();
+
         if (ringingCall != null && foregroundCall != null && backgroundCall != null) {
-            mTelephonyRegistryMgr.notifyPreciseCallState(sender.getPhoneId(), sender.getSubId(),
-                    convertPreciseCallState(ringingCall.getState()),
+            int[] callStates = {convertPreciseCallState(ringingCall.getState()),
                     convertPreciseCallState(foregroundCall.getState()),
-                    convertPreciseCallState(backgroundCall.getState()));
+                    convertPreciseCallState(backgroundCall.getState())};
+            mTelephonyRegistryMgr.notifyPreciseCallState(sender.getPhoneId(), sender.getSubId(),
+                    callStates, imsCallIds, imsCallServiceTypes, imsCallTypes);
         }
     }
 
@@ -223,6 +241,12 @@
     }
 
     @Override
+    public void notifyMediaQualityStatusChanged(Phone sender, MediaQualityStatus status) {
+        mTelephonyRegistryMgr.notifyMediaQualityStatusChanged(
+                sender.getPhoneId(), sender.getSubId(), status);
+    }
+
+    @Override
     public void notifyRegistrationFailed(Phone sender, @NonNull CellIdentity cellIdentity,
             @NonNull String chosenPlmn, int domain, int causeCode, int additionalCauseCode) {
         mTelephonyRegistryMgr.notifyRegistrationFailed(sender.getPhoneId(), sender.getSubId(),
@@ -262,6 +286,18 @@
                 sender.getSubId(), linkCapacityEstimateList);
     }
 
+    @Override
+    public void notifyCallbackModeStarted(Phone sender, @EmergencyCallbackModeType int type) {
+        mTelephonyRegistryMgr.notifyCallBackModeStarted(sender.getPhoneId(),
+                sender.getSubId(), type);
+    }
+
+    @Override
+    public void notifyCallbackModeStopped(Phone sender, @EmergencyCallbackModeType int type,
+            @EmergencyCallbackModeStopReason int reason) {
+        mTelephonyRegistryMgr.notifyCallbackModeStopped(sender.getPhoneId(),
+                sender.getSubId(), type, reason);
+    }
     /**
      * Convert the {@link Call.State} enum into the PreciseCallState.PRECISE_CALL_STATE_* constants
      * for the public API.
diff --git a/src/java/com/android/internal/telephony/DeviceStateMonitor.java b/src/java/com/android/internal/telephony/DeviceStateMonitor.java
index 3d63a29..ecc6208 100644
--- a/src/java/com/android/internal/telephony/DeviceStateMonitor.java
+++ b/src/java/com/android/internal/telephony/DeviceStateMonitor.java
@@ -20,6 +20,7 @@
 import static android.hardware.radio.V1_0.DeviceStateType.CHARGING_STATE;
 import static android.hardware.radio.V1_0.DeviceStateType.LOW_DATA_EXPECTED;
 import static android.hardware.radio.V1_0.DeviceStateType.POWER_SAVE_MODE;
+import static android.telephony.TelephonyManager.HAL_SERVICE_NETWORK;
 
 import android.app.UiModeManager;
 import android.content.BroadcastReceiver;
@@ -669,7 +670,7 @@
                 LINK_CAPACITY_UPLINK_THRESHOLDS, AccessNetworkType.EUTRAN);
         mPhone.setLinkCapacityReportingCriteria(LINK_CAPACITY_DOWNLINK_THRESHOLDS,
                 LINK_CAPACITY_UPLINK_THRESHOLDS, AccessNetworkType.CDMA2000);
-        if (mPhone.getHalVersion().greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) {
+        if (mPhone.getHalVersion(HAL_SERVICE_NETWORK).greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) {
             mPhone.setLinkCapacityReportingCriteria(LINK_CAPACITY_DOWNLINK_THRESHOLDS,
                     LINK_CAPACITY_UPLINK_THRESHOLDS, AccessNetworkType.NGRAN);
         }
diff --git a/src/java/com/android/internal/telephony/DisplayInfoController.java b/src/java/com/android/internal/telephony/DisplayInfoController.java
index f1e2608..567331d 100644
--- a/src/java/com/android/internal/telephony/DisplayInfoController.java
+++ b/src/java/com/android/internal/telephony/DisplayInfoController.java
@@ -21,9 +21,7 @@
 import android.os.Message;
 import android.os.Registrant;
 import android.os.RegistrantList;
-import android.telephony.AccessNetworkConstants;
 import android.telephony.AnomalyReporter;
-import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager;
@@ -47,8 +45,6 @@
  * TelephonyDisplayInfo via {@link #getTelephonyDisplayInfo}.
  */
 public class DisplayInfoController extends Handler {
-    private static final String TAG = "DisplayInfoController";
-
     private final String mLogTag;
     private final LocalLog mLocalLog = new LocalLog(128);
 
@@ -106,12 +102,10 @@
      * NetworkTypeController.
      */
     public void updateTelephonyDisplayInfo() {
-        NetworkRegistrationInfo nri =  mPhone.getServiceState().getNetworkRegistrationInfo(
-                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-        int dataNetworkType = nri == null ? TelephonyManager.NETWORK_TYPE_UNKNOWN
-                : nri.getAccessNetworkTechnology();
-        TelephonyDisplayInfo newDisplayInfo = new TelephonyDisplayInfo(dataNetworkType,
-                mNetworkTypeController.getOverrideNetworkType(), mServiceState.getRoaming());
+        TelephonyDisplayInfo newDisplayInfo = new TelephonyDisplayInfo(
+                mNetworkTypeController.getDataNetworkType(),
+                mNetworkTypeController.getOverrideNetworkType(),
+                mServiceState.getRoaming());
         if (!newDisplayInfo.equals(mTelephonyDisplayInfo)) {
             logl("TelephonyDisplayInfo changed from " + mTelephonyDisplayInfo + " to "
                     + newDisplayInfo);
@@ -149,7 +143,7 @@
             }
         } catch (InvalidArgumentException e) {
             logel(e.getMessage());
-            AnomalyReporter.reportAnomaly(UUID.fromString("3aa92a2c-94ed-46a0-a744-d6b1dfec2a55"),
+            AnomalyReporter.reportAnomaly(UUID.fromString("3aa92a2c-94ed-46a0-a744-d6b1dfec2a56"),
                     e.getMessage(), mPhone.getCarrierId());
         }
     }
diff --git a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
old mode 100755
new mode 100644
index 7738b44..d76ee19
--- a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
@@ -45,6 +45,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.PhoneInternalInterface.DialArgs;
 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.metrics.TelephonyMetrics;
 import com.android.telephony.Rlog;
 
@@ -480,16 +482,29 @@
             disableDataCallInEmergencyCall(dialString);
 
             // In Ecm mode, if another emergency call is dialed, Ecm mode will not exit.
-            if(!isPhoneInEcmMode || (isPhoneInEcmMode && isEmergencyCall)) {
+            if (!isPhoneInEcmMode || (isPhoneInEcmMode && isEmergencyCall)) {
                 mCi.dial(mPendingMO.getAddress(), mPendingMO.isEmergencyCall(),
                         mPendingMO.getEmergencyNumberInfo(),
-                        mPendingMO.hasKnownUserIntentEmergency(),
-                        clirMode, obtainCompleteMessage());
+                        mPendingMO.hasKnownUserIntentEmergency(), clirMode,
+                        obtainCompleteMessage());
+            } else if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
+                mPendingCallInEcm = true;
+                final int finalClirMode = clirMode;
+                Runnable onComplete = new Runnable() {
+                    @Override
+                    public void run() {
+                        mCi.dial(mPendingMO.getAddress(), mPendingMO.isEmergencyCall(),
+                        mPendingMO.getEmergencyNumberInfo(),
+                        mPendingMO.hasKnownUserIntentEmergency(), finalClirMode,
+                        obtainCompleteMessage());
+                    }
+                };
+                EmergencyStateTracker.getInstance().exitEmergencyCallbackMode(onComplete);
             } else {
                 mPhone.exitEmergencyCallbackMode();
-                mPhone.setOnEcbModeExitResponse(this,EVENT_EXIT_ECM_RESPONSE_CDMA, null);
-                mPendingCallClirMode=clirMode;
-                mPendingCallInEcm=true;
+                mPhone.setOnEcbModeExitResponse(this, EVENT_EXIT_ECM_RESPONSE_CDMA, null);
+                mPendingCallClirMode = clirMode;
+                mPendingCallInEcm = true;
             }
         }
 
@@ -959,6 +974,8 @@
                             } else {
                                 newUnknownConnectionCdma = mConnections[i];
                             }
+                        } else if (hangupWaitingCallSilently(i)) {
+                            return;
                         }
                     }
                 }
@@ -1010,6 +1027,9 @@
 
                 if (mConnections[i].getCall() == mRingingCall) {
                     newRinging = mConnections[i];
+                    if (hangupWaitingCallSilently(i)) {
+                        return;
+                    }
                 } // else something strange happened
                 hasNonHangupStateChanged = true;
             } else if (conn != null && dc != null) { /* implicit conn.compareTo(dc) */
@@ -1819,6 +1839,10 @@
     }
 
     private boolean isEmcRetryCause(int causeCode) {
+        if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
+            log("isEmcRetryCause AP based domain selection ignores the cause");
+            return false;
+        }
         if (causeCode == CallFailCause.EMC_REDIAL_ON_IMS ||
             causeCode == CallFailCause.EMC_REDIAL_ON_VOWIFI) {
             return true;
@@ -1898,4 +1922,22 @@
     public void cleanupCalls() {
         pollCallsWhenSafe();
     }
+
+    private boolean hangupWaitingCallSilently(int index) {
+        if (index < 0 || index >= mConnections.length) return false;
+
+        GsmCdmaConnection newRinging = mConnections[index];
+        if (newRinging == null) return false;
+
+        if ((mPhone.getTerminalBasedCallWaitingState(true)
+                        == CallWaitingController.TERMINAL_BASED_NOT_ACTIVATED)
+                && (newRinging.getState() == Call.State.WAITING)) {
+            Rlog.d(LOG_TAG, "hangupWaitingCallSilently");
+            newRinging.dispose();
+            mConnections[index] = null;
+            mCi.hangupWaitingOrBackground(obtainCompleteMessage());
+            return true;
+        }
+        return false;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
index 8850f93..5eae061 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
@@ -16,6 +16,11 @@
 
 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;
 import static com.android.internal.telephony.CommandsInterface.CF_ACTION_DISABLE;
@@ -32,6 +37,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.ContentValues;
@@ -40,6 +46,7 @@
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
 import android.database.SQLException;
+import android.hardware.radio.modem.ImeiInfo;
 import android.net.Uri;
 import android.os.AsyncResult;
 import android.os.Build;
@@ -57,6 +64,7 @@
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.preference.PreferenceManager;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.provider.Telephony;
 import android.sysprop.TelephonyProperties;
@@ -64,10 +72,13 @@
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
+import android.telephony.AccessNetworkConstants.TransportType;
 import android.telephony.Annotation.DataActivityType;
 import android.telephony.Annotation.RadioPowerState;
+import android.telephony.AnomalyReporter;
 import android.telephony.BarringInfo;
 import android.telephony.CarrierConfigManager;
+import android.telephony.CellBroadcastIdRange;
 import android.telephony.CellIdentity;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.LinkCapacityEstimate;
@@ -81,6 +92,7 @@
 import android.telephony.TelephonyManager;
 import android.telephony.UiccAccessRule;
 import android.telephony.UssdResponse;
+import android.telephony.ims.ImsCallProfile;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
@@ -92,7 +104,9 @@
 import com.android.internal.telephony.data.AccessNetworksManager;
 import com.android.internal.telephony.data.DataNetworkController;
 import com.android.internal.telephony.data.LinkBandwidthEstimator;
+import com.android.internal.telephony.domainselection.DomainSelectionResolver;
 import com.android.internal.telephony.emergency.EmergencyNumberTracker;
+import com.android.internal.telephony.emergency.EmergencyStateTracker;
 import com.android.internal.telephony.gsm.GsmMmiCode;
 import com.android.internal.telephony.gsm.SsData;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
@@ -129,6 +143,9 @@
 import java.util.Collections;
 import java.util.Iterator;
 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;
@@ -155,6 +172,8 @@
     // Key used to read/write the SIM IMSI used for storing the voice mail
     private static final String VM_SIM_IMSI = "vm_sim_imsi_key";
     /** List of Registrants to receive Supplementary Service Notifications. */
+    // Key used to read/write the current sub Id. Updated on SIM loaded.
+    public static final String CURR_SUBID = "curr_subid";
     private RegistrantList mSsnRegistrants = new RegistrantList();
 
     //CDMA
@@ -225,10 +244,17 @@
 
     private final RegistrantList mVolteSilentRedialRegistrants = new RegistrantList();
     private DialArgs mDialArgs = null;
-
+    private final RegistrantList mEmergencyDomainSelectedRegistrants = new RegistrantList();
     private String mImei;
     private String mImeiSv;
     private String mVmNumber;
+    private int mImeiType = IMEI_TYPE_UNKNOWN;
+
+    @VisibleForTesting
+    public CellBroadcastConfigTracker mCellBroadcastConfigTracker =
+            CellBroadcastConfigTracker.make(this, null, true);
+
+    private boolean mIsNullCipherAndIntegritySupported = false;
 
     // Create Cfu (Call forward unconditional) so that dialing number &
     // mOnComplete (Message object passed by client) can be packed &
@@ -273,6 +299,7 @@
     private final CarrierPrivilegesTracker mCarrierPrivilegesTracker;
 
     private final SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionsChangedListener;
+    private final CallWaitingController mCallWaitingController;
 
     // Constructors
 
@@ -342,24 +369,22 @@
 
         mSST.registerForNetworkAttached(this, EVENT_REGISTERED_TO_NETWORK, null);
         mSST.registerForVoiceRegStateOrRatChanged(this, EVENT_VRS_OR_RAT_CHANGED, null);
+        mSST.getServiceStateStats().registerDataNetworkControllerCallback();
 
-        if (isSubscriptionManagerServiceEnabled()) {
-            mSubscriptionManagerService.registerCallback(new SubscriptionManagerServiceCallback(
-                    this::post) {
-                @Override
-                public void onUiccApplicationsEnabledChanged(int subId) {
-                    reapplyUiccAppsEnablementIfNeeded(ENABLE_UICC_APPS_MAX_RETRIES);
-                }
-            });
-        } else {
-            SubscriptionController.getInstance().registerForUiccAppsEnabled(this,
-                    EVENT_UICC_APPS_ENABLEMENT_SETTING_CHANGED, null, false);
-        }
+        mSubscriptionManagerService.registerCallback(new SubscriptionManagerServiceCallback(
+                this::post) {
+            @Override
+            public void onUiccApplicationsEnabledChanged(int subId) {
+                reapplyUiccAppsEnablementIfNeeded(ENABLE_UICC_APPS_MAX_RETRIES);
+            }
+        });
 
         mLinkBandwidthEstimator = mTelephonyComponentFactory
                 .inject(LinkBandwidthEstimator.class.getName())
                 .makeLinkBandwidthEstimator(this);
 
+        mCallWaitingController = new CallWaitingController(this);
+
         loadTtyMode();
 
         CallManager.getInstance().registerPhone(this);
@@ -397,6 +422,17 @@
                 int newPreferredTtyMode = intent.getIntExtra(
                         TelecomManager.EXTRA_TTY_PREFERRED_MODE, TelecomManager.TTY_MODE_OFF);
                 updateUiTtyMode(newPreferredTtyMode);
+            } else if (TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED.equals(action)) {
+                if (mPhoneId == intent.getIntExtra(
+                        SubscriptionManager.EXTRA_SLOT_INDEX,
+                        SubscriptionManager.INVALID_SIM_SLOT_INDEX)) {
+                    int simState = intent.getIntExtra(TelephonyManager.EXTRA_SIM_STATE,
+                            TelephonyManager.SIM_STATE_UNKNOWN);
+                    if (simState == TelephonyManager.SIM_STATE_LOADED
+                            && currentSlotSubIdChanged()) {
+                        setNetworkSelectionModeAutomatic(null);
+                    }
+                }
             }
         }
     };
@@ -453,15 +489,20 @@
         mCi.registerForLceInfo(this, EVENT_LINK_CAPACITY_CHANGED, null);
         mCi.registerForCarrierInfoForImsiEncryption(this,
                 EVENT_RESET_CARRIER_KEY_IMSI_ENCRYPTION, null);
+        mCi.registerForTriggerImsDeregistration(this, EVENT_IMS_DEREGISTRATION_TRIGGERED, null);
+        mCi.registerForNotifyAnbr(this, EVENT_TRIGGER_NOTIFY_ANBR, null);
         IntentFilter filter = new IntentFilter(
                 CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
         filter.addAction(TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED);
         filter.addAction(TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED);
+        filter.addAction(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED);
         mContext.registerReceiver(mBroadcastReceiver, filter,
                 android.Manifest.permission.MODIFY_PHONE_STATE, null, Context.RECEIVER_EXPORTED);
 
         mCDM = new CarrierKeyDownloadManager(this);
         mCIM = new CarrierInfoManager();
+
+        initializeCarrierApps();
     }
 
     private void initRatSpecific(int precisePhoneType) {
@@ -484,8 +525,12 @@
             // This is needed to handle phone process crashes
             mIsPhoneInEcmState = getInEcmMode();
             if (mIsPhoneInEcmState) {
-                // Send a message which will invoke handleExitEmergencyCallbackMode
-                mCi.exitEmergencyCallbackMode(null);
+                if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
+                    EmergencyStateTracker.getInstance().exitEmergencyCallbackMode();
+                } else {
+                    // Send a message which will invoke handleExitEmergencyCallbackMode
+                    mCi.exitEmergencyCallbackMode(null);
+                }
             }
 
             mCi.setPhoneType(PhoneConstants.PHONE_TYPE_CDMA);
@@ -508,11 +553,7 @@
                 logd("update icc_operator_numeric=" + operatorNumeric);
                 tm.setSimOperatorNumericForPhone(mPhoneId, operatorNumeric);
 
-                if (isSubscriptionManagerServiceEnabled()) {
-                    mSubscriptionManagerService.setMccMnc(getSubId(), operatorNumeric);
-                } else {
-                    SubscriptionController.getInstance().setMccMnc(operatorNumeric, getSubId());
-                }
+                mSubscriptionManagerService.setMccMnc(getSubId(), operatorNumeric);
 
                 // Sets iso country property by retrieving from build-time system property
                 String iso = "";
@@ -524,11 +565,7 @@
 
                 logd("init: set 'gsm.sim.operator.iso-country' to iso=" + iso);
                 tm.setSimCountryIsoForPhone(mPhoneId, iso);
-                if (isSubscriptionManagerServiceEnabled()) {
-                    mSubscriptionManagerService.setCountryIso(getSubId(), iso);
-                } else {
-                    SubscriptionController.getInstance().setCountryIso(iso, getSubId());
-                }
+                mSubscriptionManagerService.setCountryIso(getSubId(), iso);
 
                 // Updates MCC MNC device configuration information
                 logd("update mccmnc=" + operatorNumeric);
@@ -540,6 +577,31 @@
         }
     }
 
+    /**
+     * Initialize the carrier apps.
+     */
+    private void initializeCarrierApps() {
+        // Only perform on the default phone. There is no need to do it twice on the DSDS device.
+        if (mPhoneId != 0) return;
+
+        logd("initializeCarrierApps");
+        mContext.registerReceiverForAllUsers(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                // Remove this line after testing
+                if (Intent.ACTION_USER_FOREGROUND.equals(intent.getAction())) {
+                    UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER);
+                    // If couldn't get current user ID, guess it's 0.
+                    CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(),
+                            TelephonyManager.getDefault(),
+                            userHandle != null ? userHandle.getIdentifier() : 0, mContext);
+                }
+            }
+        }, new IntentFilter(Intent.ACTION_USER_FOREGROUND), null, null);
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(),
+                TelephonyManager.getDefault(), ActivityManager.getCurrentUser(), mContext);
+    }
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public boolean isPhoneTypeGsm() {
         return mPrecisePhoneType == PhoneConstants.PHONE_TYPE_GSM;
@@ -737,7 +799,10 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public void notifyPreciseCallStateChanged() {
         /* we'd love it if this was package-scoped*/
-        super.notifyPreciseCallStateChangedP();
+        AsyncResult ar = new AsyncResult(null, this, null);
+        mPreciseCallStateRegistrants.notifyRegistrants(ar);
+
+        mNotifier.notifyPreciseCallState(this, null, null, null);
     }
 
     public void notifyNewRingingConnection(Connection c) {
@@ -1332,8 +1397,7 @@
         // emergency number list on another SIM, but is not on theirs.  In this case we will use the
         // emergency number list for this carrier's SIM only.
         if (useOnlyDialedSimEccList) {
-            isEmergency = getEmergencyNumberTracker().isEmergencyNumber(dialString,
-                    true /* exactMatch */);
+            isEmergency = getEmergencyNumberTracker().isEmergencyNumber(dialString);
             logi("dial; isEmergency=" + isEmergency
                     + " (based on this phone only); globalIsEmergency="
                     + tm.isEmergencyNumber(dialString));
@@ -1342,6 +1406,12 @@
             logi("dial; isEmergency=" + isEmergency + " (based on all phones)");
         }
 
+        // Undetectable emergeny number indicated by new domain selection service
+        if (dialArgs.isEmergency) {
+            logi("dial; isEmergency=" + isEmergency + " (domain selection module)");
+            isEmergency = true;
+        }
+
         /** Check if the call is Wireless Priority Service call */
         boolean isWpsCall = dialString != null ? (dialString.startsWith(PREFIX_WPS)
                 || dialString.startsWith(PREFIX_WPS_CLIR_ACTIVATE)
@@ -1367,6 +1437,55 @@
         boolean useImsForCall = useImsForCall(dialArgs)
                 && (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);
+            logi("dial domain=" + domain);
+            useImsForCall = false;
+            useImsForUt = false;
+            useImsForEmergency = false;
+            if (domain == DOMAIN_PS) {
+                if (isEmergency) {
+                    useImsForEmergency = true;
+                } else if (!isMmiCode || isPotentialUssdCode) {
+                    useImsForCall = true;
+                } else {
+                    // should not reach here
+                    loge("dial unexpected Ut domain selection, ignored");
+                }
+            } else if (domain == PhoneConstants.DOMAIN_NON_3GPP_PS) {
+                if (isEmergency) {
+                    useImsForEmergency = true;
+                    extras.putString(ImsCallProfile.EXTRA_CALL_RAT_TYPE,
+                            String.valueOf(ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN));
+                } else {
+                    // should not reach here
+                    loge("dial DOMAIN_NON_3GPP_PS should be used only for emergency calls");
+                }
+            }
+
+            extras.remove(PhoneConstants.EXTRA_DIAL_DOMAIN);
+        }
+
         if (DBG) {
             logi("useImsForCall=" + useImsForCall
                     + ", useOnlyDialedSimEccList=" + useOnlyDialedSimEccList
@@ -1463,10 +1582,13 @@
         }
         if (DBG) logd("Trying (non-IMS) CS call");
         if (isDialedNumberSwapped && isEmergency) {
-            // Triggers ECM when CS call ends only for test emergency calls using
-            // ril.test.emergencynumber.
-            mIsTestingEmergencyCallbackMode = true;
-            mCi.testingEmergencyCall();
+            // If domain selection is enabled, ECM testing is handled in EmergencyStateTracker
+            if (!DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
+                // Triggers ECM when CS call ends only for test emergency calls using
+                // ril.test.emergencynumber.
+                mIsTestingEmergencyCallbackMode = true;
+                mCi.testingEmergencyCall();
+            }
         }
 
         chosenPhoneConsumer.accept(this);
@@ -1698,7 +1820,7 @@
     public void setRadioPower(boolean power, boolean forEmergencyCall,
             boolean isSelectedPhoneForEmergencyCall, boolean forceApply) {
         setRadioPowerForReason(power, forEmergencyCall, isSelectedPhoneForEmergencyCall, forceApply,
-                Phone.RADIO_POWER_REASON_USER);
+                TelephonyManager.RADIO_POWER_REASON_USER);
     }
 
     @Override
@@ -1708,6 +1830,11 @@
                 forceApply, reason);
     }
 
+    @Override
+    public Set<Integer> getRadioPowerOffReasons() {
+        return mSST.getRadioPowerOffReasons();
+    }
+
     private void storeVoiceMailNumber(String number) {
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
         SharedPreferences.Editor editor = sp.edit();
@@ -1857,6 +1984,11 @@
         return mImei;
     }
 
+    @Override
+    public int getImeiType() {
+        return mImeiType;
+    }
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @Override
     public String getEsn() {
@@ -2452,6 +2584,7 @@
         }
 
         Phone imsPhone = mImsPhone;
+
         if (useSsOverIms(onComplete)) {
             imsPhone.getOutgoingCallerIdDisplay(onComplete);
             return;
@@ -2534,6 +2667,8 @@
             return;
         }
 
+        if (mCallWaitingController.getCallWaiting(onComplete)) return;
+
         Phone imsPhone = mImsPhone;
         if (useSsOverIms(onComplete)) {
             imsPhone.getCallWaiting(onComplete);
@@ -2585,6 +2720,8 @@
             return;
         }
 
+        if (mCallWaitingController.setCallWaiting(enable, serviceClass, onComplete)) return;
+
         Phone imsPhone = mImsPhone;
         if (useSsOverIms(onComplete)) {
             imsPhone.setCallWaiting(enable, onComplete);
@@ -2615,6 +2752,23 @@
     }
 
     @Override
+    public int getTerminalBasedCallWaitingState(boolean forCsOnly) {
+        return mCallWaitingController.getTerminalBasedCallWaitingState(forCsOnly);
+    }
+
+    @Override
+    public void setTerminalBasedCallWaitingStatus(int state) {
+        if (mImsPhone != null) {
+            mImsPhone.setTerminalBasedCallWaitingStatus(state);
+        }
+    }
+
+    @Override
+    public void setTerminalBasedCallWaitingSupported(boolean supported) {
+        mCallWaitingController.setTerminalBasedCallWaitingSupported(supported);
+    }
+
+    @Override
     public void getAvailableNetworks(Message response) {
         if (isPhoneTypeGsm() || isPhoneTypeCdmaLte()) {
             Message msg = obtainMessage(EVENT_GET_AVAILABLE_NETWORKS_DONE, response);
@@ -2884,11 +3038,12 @@
 
     private void handleRadioAvailable() {
         mCi.getBasebandVersion(obtainMessage(EVENT_GET_BASEBAND_VERSION_DONE));
-
+        mCi.getImei(obtainMessage(EVENT_GET_DEVICE_IMEI_DONE));
         mCi.getDeviceIdentity(obtainMessage(EVENT_GET_DEVICE_IDENTITY_DONE));
         mCi.getRadioCapability(obtainMessage(EVENT_GET_RADIO_CAPABILITY));
         mCi.areUiccApplicationsEnabled(obtainMessage(EVENT_GET_UICC_APPS_ENABLEMENT_DONE));
 
+        handleNullCipherEnabledChange();
         startLceAfterRadioIsAvailable();
     }
 
@@ -2934,7 +3089,21 @@
                 handleRadioAvailable();
             }
             break;
-
+            case EVENT_GET_DEVICE_IMEI_DONE :
+                ar = (AsyncResult)msg.obj;
+                if (ar.exception != null || ar.result == null) {
+                    loge("Exception received : " + ar.exception);
+                    break;
+                }
+                ImeiInfo imeiInfo = (ImeiInfo) ar.result;
+                if (!TextUtils.isEmpty(imeiInfo.imei)) {
+                    mImeiType = imeiInfo.type;
+                    mImei = imeiInfo.imei;
+                    mImeiSv = imeiInfo.svn;
+                } else {
+                    // TODO Report telephony anomaly
+                }
+                break;
             case EVENT_GET_DEVICE_IDENTITY_DONE:{
                 ar = (AsyncResult)msg.obj;
 
@@ -2942,8 +3111,10 @@
                     break;
                 }
                 String[] respId = (String[])ar.result;
-                mImei = respId[0];
-                mImeiSv = respId[1];
+                if (TextUtils.isEmpty(mImei)) {
+                    mImei = respId[0];
+                    mImeiSv = respId[1];
+                }
                 mEsn  =  respId[2];
                 mMeid =  respId[3];
                 // some modems return all 0's instead of null/empty string when MEID is unavailable
@@ -2968,12 +3139,16 @@
                 logd("Event EVENT_MODEM_RESET Received" + " isInEcm = " + isInEcm()
                         + " isPhoneTypeGsm = " + isPhoneTypeGsm() + " mImsPhone = " + mImsPhone);
                 if (isInEcm()) {
-                    if (isPhoneTypeGsm()) {
-                        if (mImsPhone != null) {
-                            mImsPhone.handleExitEmergencyCallbackMode();
-                        }
+                    if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
+                        EmergencyStateTracker.getInstance().exitEmergencyCallbackMode();
                     } else {
-                        handleExitEmergencyCallbackMode(msg);
+                        if (isPhoneTypeGsm()) {
+                            if (mImsPhone != null) {
+                                mImsPhone.handleExitEmergencyCallbackMode();
+                            }
+                        } else {
+                            handleExitEmergencyCallbackMode(msg);
+                        }
                     }
                 }
             }
@@ -3343,12 +3518,64 @@
                 logd("EVENT_SUBSCRIPTIONS_CHANGED");
                 updateUsageSetting();
                 break;
+            case EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE:
+                logd("EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE");
+                ar = (AsyncResult) msg.obj;
+                if (ar == null || ar.exception == null) {
+                    mIsNullCipherAndIntegritySupported = true;
+                    return;
+                }
+                CommandException.Error error = ((CommandException) ar.exception).getCommandError();
+                mIsNullCipherAndIntegritySupported = !error.equals(
+                        CommandException.Error.REQUEST_NOT_SUPPORTED);
+                break;
 
+            case EVENT_IMS_DEREGISTRATION_TRIGGERED:
+                logd("EVENT_IMS_DEREGISTRATION_TRIGGERED");
+                ar = (AsyncResult) msg.obj;
+                if (ar.exception == null) {
+                    mImsPhone.triggerImsDeregistration(((int[]) ar.result)[0]);
+                } else {
+                    Rlog.e(LOG_TAG, "Unexpected unsol with exception", ar.exception);
+                }
+                break;
+
+            case EVENT_TRIGGER_NOTIFY_ANBR:
+                logd("EVENT_TRIGGER_NOTIFY_ANBR");
+                ar = (AsyncResult) msg.obj;
+                if (ar.exception == null) {
+                    if (mImsPhone != null) {
+                        mImsPhone.triggerNotifyAnbr(((int[]) ar.result)[0], ((int[]) ar.result)[1],
+                                ((int[]) ar.result)[2]);
+                    }
+                }
+                break;
             default:
                 super.handleMessage(msg);
         }
     }
 
+    /**
+     * Check if a different SIM is inserted at this slot from the last time. Storing last subId
+     * in SharedPreference for now to detect SIM change.
+     *
+     * @return {@code true} if current slot mapping changed; {@code false} otherwise.
+     */
+    private boolean currentSlotSubIdChanged() {
+        SharedPreferences sp =
+                PreferenceManager.getDefaultSharedPreferences(mContext);
+        int storedSubId = sp.getInt(CURR_SUBID + mPhoneId, -1);
+        boolean changed = storedSubId != getSubId();
+        if (changed) {
+            // Update stored subId
+            SharedPreferences.Editor editor = sp.edit();
+            editor.putInt(CURR_SUBID + mPhoneId, getSubId());
+            editor.apply();
+        }
+        Rlog.d(LOG_TAG, "currentSlotSubIdChanged: changed=" + changed);
+        return changed;
+    }
+
     public UiccCardApplication getUiccCardApplication() {
         if (isPhoneTypeGsm()) {
             return mUiccController.getUiccCardApplication(mPhoneId, UiccController.APP_FAM_3GPP);
@@ -3715,6 +3942,10 @@
 
     //CDMA
     private void handleEnterEmergencyCallbackMode(Message msg) {
+        if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
+            Rlog.d(LOG_TAG, "DomainSelection enabled: ignore ECBM enter event.");
+            return;
+        }
         if (DBG) {
             Rlog.d(LOG_TAG, "handleEnterEmergencyCallbackMode, isInEcm()="
                     + isInEcm());
@@ -3738,6 +3969,10 @@
 
     //CDMA
     private void handleExitEmergencyCallbackMode(Message msg) {
+        if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
+            Rlog.d(LOG_TAG, "DomainSelection enabled: ignore ECBM exit event.");
+            return;
+        }
         AsyncResult ar = (AsyncResult)msg.obj;
         if (DBG) {
             Rlog.d(LOG_TAG, "handleExitEmergencyCallbackMode,ar.exception , isInEcm="
@@ -3780,6 +4015,7 @@
      * otherwise, restart Ecm timer and notify apps the timer is restarted.
      */
     public void handleTimerInEmergencyCallbackMode(int action) {
+        if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) return;
         switch(action) {
             case CANCEL_ECM_TIMER:
                 removeCallbacks(mExitEcmRunnable);
@@ -4193,6 +4429,7 @@
     @Override
     public void setImsRegistrationState(boolean registered) {
         mSST.setImsRegistrationState(registered);
+        mCallWaitingController.setImsRegistrationState(registered);
     }
 
     @Override
@@ -4256,6 +4493,12 @@
                         + ")");
         pw.println(" mUiccApplicationsEnabled=" + mUiccApplicationsEnabled);
         pw.flush();
+        try {
+            mCallWaitingController.dump(pw);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        pw.flush();
     }
 
     @Override
@@ -4374,7 +4617,7 @@
         if (subInfo == null || TextUtils.isEmpty(subInfo.getCountryIso())) {
             return null;
         }
-        return subInfo.getCountryIso().toUpperCase();
+        return subInfo.getCountryIso().toUpperCase(Locale.ROOT);
     }
 
     public void notifyEcbmTimerReset(Boolean flag) {
@@ -4480,6 +4723,27 @@
         mVolteSilentRedialRegistrants.notifyRegistrants(ar);
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public void registerForEmergencyDomainSelected(
+            @NonNull Handler h, int what, @Nullable Object obj) {
+        mEmergencyDomainSelectedRegistrants.addUnique(h, what, obj);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void unregisterForEmergencyDomainSelected(@NonNull Handler h) {
+        mEmergencyDomainSelectedRegistrants.remove(h);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void notifyEmergencyDomainSelected(@TransportType int transportType) {
+        logd("notifyEmergencyDomainSelected transportType=" + transportType);
+        mEmergencyDomainSelectedRegistrants.notifyRegistrants(
+                new AsyncResult(null, transportType, null));
+    }
+
     /**
      * Sets the SIM voice message waiting indicator records.
      * @param line GSM Subscriber Profile Number, one-based. Only '1' is supported
@@ -4654,18 +4918,12 @@
             return;
         }
 
-        SubscriptionInfo info;
-        if (isSubscriptionManagerServiceEnabled()) {
-            info = mSubscriptionManagerService
-                    .getAllSubInfoList(mContext.getOpPackageName(), mContext.getAttributionTag())
-                    .stream()
-                    .filter(subInfo -> subInfo.getIccId().equals(IccUtils.stripTrailingFs(iccId)))
-                    .findFirst()
-                    .orElse(null);
-        } else {
-            info = SubscriptionController.getInstance().getSubInfoForIccId(
-                    IccUtils.stripTrailingFs(iccId));
-        }
+        SubscriptionInfo info = mSubscriptionManagerService
+                .getAllSubInfoList(mContext.getOpPackageName(), mContext.getAttributionTag())
+                .stream()
+                .filter(subInfo -> subInfo.getIccId().equals(IccUtils.stripTrailingFs(iccId)))
+                .findFirst()
+                .orElse(null);
 
         logd("reapplyUiccAppsEnablementIfNeeded: retries=" + retries + ", subInfo=" + info);
 
@@ -4770,18 +5028,10 @@
                 config.getBoolean(CarrierConfigManager.KEY_VONR_ON_BY_DEFAULT_BOOL);
 
         int setting = -1;
-        if (isSubscriptionManagerServiceEnabled()) {
-            SubscriptionInfoInternal subInfo = mSubscriptionManagerService
-                    .getSubscriptionInfoInternal(getSubId());
-            if (subInfo != null) {
-                setting = subInfo.getNrAdvancedCallingEnabled();
-            }
-        } else {
-            String result = SubscriptionController.getInstance().getSubscriptionProperty(
-                    getSubId(), SubscriptionManager.NR_ADVANCED_CALLING_ENABLED);
-            if (result != null) {
-                setting = Integer.parseInt(result);
-            }
+        SubscriptionInfoInternal subInfo = mSubscriptionManagerService
+                .getSubscriptionInfoInternal(getSubId());
+        if (subInfo != null) {
+            setting = subInfo.getNrAdvancedCallingEnabled();
         }
 
         logd("VoNR setting from telephony.db:"
@@ -4857,6 +5107,22 @@
     }
 
     /**
+     * Return current cell broadcast ranges.
+     */
+    public List<CellBroadcastIdRange> getCellBroadcastIdRanges() {
+        return mCellBroadcastConfigTracker.getCellBroadcastIdRanges();
+    }
+
+    /**
+     * Set reception of cell broadcast messages with the list of the given ranges.
+     */
+    @Override
+    public void setCellBroadcastIdRanges(
+            @NonNull List<CellBroadcastIdRange> ranges, Consumer<Integer> callback) {
+        mCellBroadcastConfigTracker.setCellBroadcastIdRanges(ranges, callback);
+    }
+
+    /**
      * The following function checks if supplementary service request is blocked due to FDN.
      * @param requestType request type associated with the supplementary service
      * @param serviceType supplementary service type
@@ -4867,4 +5133,21 @@
         ArrayList<String> controlStrings = GsmMmiCode.getControlStrings(requestType, serviceType);
         return FdnUtils.isSuppServiceRequestBlockedByFdn(mPhoneId, controlStrings, getCountryIso());
     }
-}
\ No newline at end of file
+
+    @Override
+    public void handleNullCipherEnabledChange() {
+        if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CELLULAR_SECURITY,
+                TelephonyManager.PROPERTY_ENABLE_NULL_CIPHER_TOGGLE, true)) {
+            logi("Not handling null cipher update. Feature disabled by DeviceConfig.");
+            return;
+        }
+        mCi.setNullCipherAndIntegrityEnabled(
+                getNullCipherAndIntegrityEnabledPreference(),
+                obtainMessage(EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE));
+    }
+
+    @Override
+    public boolean isNullCipherAndIntegritySupported() {
+        return mIsNullCipherAndIntegritySupported;
+    }
+}
diff --git a/src/java/com/android/internal/telephony/HalVersion.java b/src/java/com/android/internal/telephony/HalVersion.java
index f83d790..c05111b 100644
--- a/src/java/com/android/internal/telephony/HalVersion.java
+++ b/src/java/com/android/internal/telephony/HalVersion.java
@@ -25,6 +25,9 @@
  */
 public class HalVersion implements Comparable<HalVersion> {
 
+    /** The HAL Version indicating that the version is unsupported */
+    public static final HalVersion UNSUPPORTED = new HalVersion(-2, -2);
+
     /** The HAL Version indicating that the version is unknown or invalid */
     public static final HalVersion UNKNOWN = new HalVersion(-1, -1);
 
diff --git a/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java b/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
index 742cc90..ab62aa4 100644
--- a/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
+++ b/src/java/com/android/internal/telephony/IccPhoneBookInterfaceManager.java
@@ -39,6 +39,7 @@
 import com.android.telephony.Rlog;
 
 import java.util.List;
+import java.util.Locale;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -337,7 +338,10 @@
         }
 
         efid = updateEfForIccType(efid);
-        if (DBG) logd("getAdnRecordsInEF: efid=0x" + Integer.toHexString(efid).toUpperCase());
+        if (DBG) {
+            logd("getAdnRecordsInEF: efid=0x" + Integer.toHexString(efid)
+                    .toUpperCase(Locale.ROOT));
+        }
 
         checkThread();
         Request loadRequest = new Request();
diff --git a/src/java/com/android/internal/telephony/IccProvider.java b/src/java/com/android/internal/telephony/IccProvider.java
index 7a128c0..b7c7e7b 100644
--- a/src/java/com/android/internal/telephony/IccProvider.java
+++ b/src/java/com/android/internal/telephony/IccProvider.java
@@ -37,6 +37,7 @@
 import com.android.telephony.Rlog;
 
 import java.util.List;
+import java.util.Locale;
 
 /**
  * {@hide}
@@ -424,7 +425,7 @@
 
     private MatrixCursor loadFromEf(int efType, int subId) {
         if (DBG) log("loadFromEf: efType=0x" +
-                Integer.toHexString(efType).toUpperCase() + ", subscription=" + subId);
+                Integer.toHexString(efType).toUpperCase(Locale.ROOT) + ", subscription=" + subId);
 
         List<AdnRecord> adnRecords = null;
         try {
diff --git a/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
index d3e6a0d..2d77631 100644
--- a/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
+++ b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
@@ -176,6 +176,41 @@
         mSmsPermissions = smsPermissions;
     }
 
+    /**
+     * PhoneFactory Dependencies for testing.
+     */
+    @VisibleForTesting
+    public interface PhoneFactoryProxy {
+        Phone getPhone(int index);
+        Phone getDefaultPhone();
+        Phone[] getPhones();
+    }
+
+    private PhoneFactoryProxy mPhoneFactoryProxy = new PhoneFactoryProxy() {
+        @Override
+        public Phone getPhone(int index) {
+            return PhoneFactory.getPhone(index);
+        }
+
+        @Override
+        public Phone getDefaultPhone() {
+            return PhoneFactory.getDefaultPhone();
+        }
+
+        @Override
+        public Phone[] getPhones() {
+            return PhoneFactory.getPhones();
+        }
+    };
+
+    /**
+     * Overrides PhoneFactory dependencies for testing.
+     */
+    @VisibleForTesting
+    public void setPhoneFactoryProxy(PhoneFactoryProxy proxy) {
+        mPhoneFactoryProxy = proxy;
+    }
+
     private void enforceNotOnHandlerThread(String methodName) {
         if (Looper.myLooper() == mHandler.getLooper()) {
             throw new RuntimeException("This method " + methodName + " will deadlock if called from"
@@ -457,11 +492,11 @@
      */
     public void sendText(String callingPackage, String destAddr, String scAddr,
             String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
-            boolean persistMessageForNonDefaultSmsApp, long messageId) {
+            boolean persistMessageForNonDefaultSmsApp, long messageId, boolean skipShortCodeCheck) {
         sendTextInternal(callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
                 persistMessageForNonDefaultSmsApp, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
                 false /* expectMore */, SMS_MESSAGE_PERIOD_NOT_SPECIFIED, false /* isForVvm */,
-                messageId);
+                messageId, skipShortCodeCheck);
     }
 
     /**
@@ -481,6 +516,16 @@
                 SMS_MESSAGE_PERIOD_NOT_SPECIFIED, isForVvm, 0L /* messageId */);
     }
 
+
+    private void sendTextInternal(String callingPackage, String destAddr, String scAddr,
+            String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
+            boolean persistMessageForNonDefaultSmsApp, int priority, boolean expectMore,
+            int validityPeriod, boolean isForVvm, long messageId) {
+        sendTextInternal(callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
+                persistMessageForNonDefaultSmsApp, priority, expectMore, validityPeriod, isForVvm,
+                messageId, false);
+    }
+
     /**
      * Send a text based SMS.
      *
@@ -527,12 +572,13 @@
      *  Any Other values including negative considered as Invalid Validity Period of the message.
      * @param messageId An id that uniquely identifies the message requested to be sent.
      *                 Used for logging and diagnostics purposes. The id may be 0.
+     * @param skipShortCodeCheck Skip check for short code type destination address.
      */
 
     private void sendTextInternal(String callingPackage, String destAddr, String scAddr,
             String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
             boolean persistMessageForNonDefaultSmsApp, int priority, boolean expectMore,
-            int validityPeriod, boolean isForVvm, long messageId) {
+            int validityPeriod, boolean isForVvm, long messageId, boolean skipShortCodeCheck) {
         if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
             log("sendText: destAddr=" + destAddr + " scAddr=" + scAddr
                     + " text='" + text + "' sentIntent=" + sentIntent + " deliveryIntent="
@@ -544,7 +590,7 @@
         destAddr = filterDestAddress(destAddr);
         mDispatchersController.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
                 null/*messageUri*/, callingPackage, persistMessageForNonDefaultSmsApp,
-                priority, expectMore, validityPeriod, isForVvm, messageId);
+                priority, expectMore, validityPeriod, isForVvm, messageId, skipShortCodeCheck);
     }
 
     /**
@@ -862,6 +908,7 @@
     public String getSmscAddressFromIccEf(String callingPackage) {
         if (!mSmsPermissions.checkCallingOrSelfCanGetSmscAddress(
                 callingPackage, "getSmscAddressFromIccEf")) {
+            loge("Caller do not have permission to call GetSmscAddress");
             return null;
         }
         enforceNotOnHandlerThread("getSmscAddressFromIccEf");
@@ -883,6 +930,7 @@
     public boolean setSmscAddressOnIccEf(String callingPackage, String smsc) {
         if (!mSmsPermissions.checkCallingOrSelfCanSetSmscAddress(
                 callingPackage, "setSmscAddressOnIccEf")) {
+            loge("Caller do not have permission to call SetSmscAddress");
             return false;
         }
         enforceNotOnHandlerThread("setSmscAddressOnIccEf");
@@ -1452,11 +1500,27 @@
         return null;
     }
 
-    private void notifyIfOutgoingEmergencySms(String destAddr) {
+    @VisibleForTesting
+    public void notifyIfOutgoingEmergencySms(String destAddr) {
+        Phone[] allPhones = mPhoneFactoryProxy.getPhones();
         EmergencyNumber emergencyNumber = mPhone.getEmergencyNumberTracker().getEmergencyNumber(
                 destAddr);
         if (emergencyNumber != null) {
             mPhone.notifyOutgoingEmergencySms(emergencyNumber);
+        } else if (allPhones.length > 1) {
+            // If there are multiple active SIMs, check all instances:
+            for (Phone phone : allPhones) {
+                // If the current iteration was already checked, skip:
+                if (phone.getPhoneId() == mPhone.getPhoneId()) {
+                    continue;
+                }
+                emergencyNumber = phone.getEmergencyNumberTracker()
+                        .getEmergencyNumber(destAddr);
+                if (emergencyNumber != null) {
+                    mPhone.notifyOutgoingEmergencySms(emergencyNumber);
+                    break;
+                }
+            }
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/ImsIndication.java b/src/java/com/android/internal/telephony/ImsIndication.java
new file mode 100644
index 0000000..00652f8
--- /dev/null
+++ b/src/java/com/android/internal/telephony/ImsIndication.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2022 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 android.telephony.TelephonyManager.HAL_SERVICE_IMS;
+
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CONNECTION_SETUP_FAILURE;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_NOTIFY_ANBR;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_TRIGGER_IMS_DEREGISTRATION;
+
+import android.hardware.radio.ims.IRadioImsIndication;
+import android.os.AsyncResult;
+import android.telephony.ims.feature.ConnectionFailureInfo;
+
+/**
+ * Interface declaring unsolicited radio indications for IMS APIs.
+ */
+public class ImsIndication extends IRadioImsIndication.Stub {
+    private final RIL mRil;
+
+    public ImsIndication(RIL ril) {
+        mRil = ril;
+    }
+
+    @Override
+    public String getInterfaceHash() {
+        return IRadioImsIndication.HASH;
+    }
+
+    @Override
+    public int getInterfaceVersion() {
+        return IRadioImsIndication.VERSION;
+    }
+
+    /**
+     * Fired by radio when any IMS traffic is not sent to network due to any failure
+     * on cellular networks.
+     *
+     * @param indicationType Type of radio indication.
+     * @param token The token provided by {@link #startImsTraffic}.
+     * @param failureInfo Connection failure information.
+     */
+    public void onConnectionSetupFailure(int indicationType, int token,
+            android.hardware.radio.ims.ConnectionFailureInfo failureInfo) {
+        mRil.processIndication(HAL_SERVICE_IMS, indicationType);
+
+        Object[] response = new Object[2];
+        response[0] = token;
+        response[1] = new ConnectionFailureInfo(
+                RILUtils.convertHalConnectionFailureReason(failureInfo.failureReason),
+                failureInfo.causeCode, failureInfo.waitTimeMillis);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CONNECTION_SETUP_FAILURE, response);
+
+        mRil.mConnectionSetupFailureRegistrants.notifyRegistrants(
+                new AsyncResult(null, response, null));
+    }
+
+    /**
+     * Fired by radio when ANBR is received form the network.
+     *
+     * @param indicationType Type of radio indication.
+     * @param qosSessionId QoS session ID is used to identify media stream such as audio or video.
+     * @param imsdirection Direction of this packet stream (e.g. uplink or downlink).
+     * @param bitsPerSecond The recommended bit rate for the UE
+     *        for a specific logical channel and a specific direction by the network.
+     */
+    public void notifyAnbr(int indicationType, int qosSessionId, int imsdirection,
+            int bitsPerSecond) {
+        mRil.processIndication(HAL_SERVICE_IMS, indicationType);
+
+        int[] response = new int[3];
+        response[0] = qosSessionId;
+        response[1] = imsdirection;
+        response[2] = bitsPerSecond;
+
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_NOTIFY_ANBR, response);
+
+        mRil.mNotifyAnbrRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
+    }
+
+    /**
+     * Requests IMS stack to perform graceful IMS deregistration before radio performing
+     * network detach in the events of SIM remove, refresh or and so on. The radio waits for
+     * the IMS deregistration, which will be notified by telephony via
+     * {@link IRadioIms#updateImsRegistrationInfo()}, or a certain timeout interval to start
+     * the network detach procedure.
+     *
+     * @param indicationType Type of radio indication.
+     * @param reason the reason why the deregistration is triggered.
+     */
+    public void triggerImsDeregistration(int indicationType, int reason) {
+        mRil.processIndication(HAL_SERVICE_IMS, indicationType);
+
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_TRIGGER_IMS_DEREGISTRATION, reason);
+
+        int[] response = new int[1];
+        response[0] = RILUtils.convertHalDeregistrationReason(reason);
+
+        mRil.mTriggerImsDeregistrationRegistrants.notifyRegistrants(
+                new AsyncResult(null, response, null));
+    }
+}
diff --git a/src/java/com/android/internal/telephony/ImsResponse.java b/src/java/com/android/internal/telephony/ImsResponse.java
new file mode 100644
index 0000000..1adc000
--- /dev/null
+++ b/src/java/com/android/internal/telephony/ImsResponse.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2022 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 android.telephony.TelephonyManager.HAL_SERVICE_IMS;
+import static android.telephony.ims.feature.MmTelFeature.ImsTrafficSessionCallbackWrapper.INVALID_TOKEN;
+
+import android.hardware.radio.RadioError;
+import android.hardware.radio.RadioResponseInfo;
+import android.hardware.radio.ims.IRadioImsResponse;
+import android.telephony.ims.feature.ConnectionFailureInfo;
+
+/**
+ * Interface declaring response functions to solicited radio requests for IMS APIs.
+ */
+public class ImsResponse extends IRadioImsResponse.Stub {
+    private final RIL mRil;
+
+    public ImsResponse(RIL ril) {
+        mRil = ril;
+    }
+
+    @Override
+    public String getInterfaceHash() {
+        return IRadioImsResponse.HASH;
+    }
+
+    @Override
+    public int getInterfaceVersion() {
+        return IRadioImsResponse.VERSION;
+    }
+
+    /**
+     * @param info Response info struct containing response type, serial no. and error.
+     */
+    public void setSrvccCallInfoResponse(RadioResponseInfo info) {
+        RadioResponse.responseVoid(HAL_SERVICE_IMS, mRil, info);
+    }
+
+    /**
+     * @param info Response info struct containing response type, serial no. and error.
+     */
+    public void updateImsRegistrationInfoResponse(RadioResponseInfo info) {
+        RadioResponse.responseVoid(HAL_SERVICE_IMS, mRil, info);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error.
+     * @param failureInfo Failure information.
+     */
+    public void startImsTrafficResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.ims.ConnectionFailureInfo failureInfo) {
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_IMS, responseInfo);
+
+        if (rr != null) {
+            Object[] response = { new Integer(INVALID_TOKEN), null };
+            if (responseInfo.error == RadioError.NONE) {
+                if (failureInfo != null) {
+                    response[1] = new ConnectionFailureInfo(
+                            RILUtils.convertHalConnectionFailureReason(failureInfo.failureReason),
+                            failureInfo.causeCode, failureInfo.waitTimeMillis);
+                }
+                RadioResponse.sendMessageResponse(rr.mResult, response);
+            }
+            mRil.processResponseDone(rr, responseInfo, response);
+        }
+    }
+
+    /**
+     * @param info Response info struct containing response type, serial no. and error.
+     */
+    public void stopImsTrafficResponse(RadioResponseInfo info) {
+        RadioResponse.responseVoid(HAL_SERVICE_IMS, mRil, info);
+    }
+
+    /**
+     * @param info Response info struct containing response type, serial no. and error.
+     */
+    public void triggerEpsFallbackResponse(RadioResponseInfo info) {
+        RadioResponse.responseVoid(HAL_SERVICE_IMS, mRil, info);
+    }
+
+    /**
+     * @param info Response info struct containing response type, serial no. and error.
+     */
+    public void sendAnbrQueryResponse(RadioResponseInfo info) {
+        RadioResponse.responseVoid(HAL_SERVICE_IMS, mRil, info);
+    }
+
+    /**
+     * @param info Response info struct containing response type, serial no. and error.
+     */
+    public void updateImsCallStatusResponse(RadioResponseInfo info) {
+        RadioResponse.responseVoid(HAL_SERVICE_IMS, mRil, info);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/ImsSmsDispatcher.java b/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
index b9f896c..90885fe 100644
--- a/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
+++ b/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony;
 
+import android.app.Activity;
 import android.content.Context;
 import android.os.Binder;
 import android.os.Message;
@@ -43,7 +44,9 @@
 import com.android.internal.telephony.util.SMSDispatcherUtil;
 import com.android.telephony.Rlog;
 
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
@@ -58,6 +61,7 @@
 
     private static final String TAG = "ImsSmsDispatcher";
     private static final int CONNECT_DELAY_MS = 5000; // 5 seconds;
+    public static final int MAX_SEND_RETRIES_OVER_IMS = MAX_SEND_RETRIES;
 
     /**
      * Creates FeatureConnector instances for ImsManager, used during testing to inject mock
@@ -72,6 +76,7 @@
                 FeatureConnector.Listener<ImsManager> listener, Executor executor);
     }
 
+    public List<Integer> mMemoryAvailableNotifierList = new ArrayList<Integer>();
     @VisibleForTesting
     public Map<Integer, SmsTracker> mTrackers = new ConcurrentHashMap<>();
     @VisibleForTesting
@@ -140,6 +145,37 @@
 
     private final IImsSmsListener mImsSmsListener = new IImsSmsListener.Stub() {
         @Override
+        public void onMemoryAvailableResult(int token, @SendStatusResult int status,
+                int networkReasonCode) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                logd("onMemoryAvailableResult token=" + token + " status=" + status
+                        + " networkReasonCode=" + networkReasonCode);
+                if (!mMemoryAvailableNotifierList.contains(token)) {
+                    loge("onMemoryAvailableResult Invalid token");
+                    return;
+                }
+                mMemoryAvailableNotifierList.remove(Integer.valueOf(token));
+
+                /**
+                 * The Retrans flag is set and reset As per section 6.3.3.1.2 in TS 124011
+                 * Note: Assuming that SEND_STATUS_ERROR_RETRY is sent in case of temporary failure
+                 */
+                if (status ==  ImsSmsImplBase.SEND_STATUS_ERROR_RETRY) {
+                    if (!mRPSmmaRetried) {
+                        sendMessageDelayed(obtainMessage(EVENT_RETRY_SMMA), SEND_RETRY_DELAY);
+                        mRPSmmaRetried = true;
+                    } else {
+                        mRPSmmaRetried = false;
+                    }
+                } else {
+                    mRPSmmaRetried = false;
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+        @Override
         public void onSendSmsResult(int token, int messageRef, @SendStatusResult int status,
                 @SmsManager.Result int reason, int networkReasonCode) {
             final long identity = Binder.clearCallingIdentity();
@@ -170,10 +206,19 @@
                         mTrackers.remove(token);
                         break;
                     case ImsSmsImplBase.SEND_STATUS_ERROR_RETRY:
-                        if (tracker.mRetryCount < MAX_SEND_RETRIES) {
+                        int maxRetryCountOverIms = getMaxRetryCountOverIms();
+                        if (tracker.mRetryCount < getMaxSmsRetryCount()) {
+                            if (maxRetryCountOverIms < getMaxSmsRetryCount()
+                                    && tracker.mRetryCount >= maxRetryCountOverIms) {
+                                tracker.mRetryCount += 1;
+                                mTrackers.remove(token);
+                                fallbackToPstn(tracker);
+                                break;
+                            }
                             tracker.mRetryCount += 1;
                             sendMessageDelayed(
-                                    obtainMessage(EVENT_SEND_RETRY, tracker), SEND_RETRY_DELAY);
+                                    obtainMessage(EVENT_SEND_RETRY, tracker),
+                                    getSmsRetryDelayValue());
                         } else {
                             tracker.onFailed(mContext, reason, networkReasonCode);
                             mTrackers.remove(token);
@@ -193,6 +238,7 @@
                         SmsConstants.FORMAT_3GPP2.equals(getFormat()),
                         status == ImsSmsImplBase.SEND_STATUS_ERROR_FALLBACK,
                         reason,
+                        networkReasonCode,
                         tracker.mMessageId,
                         tracker.isFromDefaultSmsApplication(mContext),
                         tracker.getInterval());
@@ -248,6 +294,10 @@
                             mappedResult =
                                     ImsSmsImplBase.DELIVER_STATUS_ERROR_REQUEST_NOT_SUPPORTED;
                             break;
+                        case Activity.RESULT_OK:
+                            // class2 message saving to SIM operation is in progress, defer ack
+                            // until saving to SIM is success/failure
+                            return;
                         default:
                             mappedResult = ImsSmsImplBase.DELIVER_STATUS_ERROR_GENERIC;
                             break;
@@ -263,7 +313,7 @@
                     } catch (ImsException e) {
                         loge("Failed to acknowledgeSms(). Error: " + e.getMessage());
                     }
-                }, true /* ignoreClass */, true /* isOverIms */);
+                }, true /* ignoreClass */, true /* isOverIms */, token);
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
@@ -277,6 +327,10 @@
                 logd("SMS retry..");
                 sendSms((SmsTracker) msg.obj);
                 break;
+            case EVENT_RETRY_SMMA:
+                logd("SMMA Notification retry..");
+                onMemoryAvailable();
+                break;
             default:
                 super.handleMessage(msg);
         }
@@ -295,6 +349,9 @@
                             mImsManager = manager;
                             setListeners();
                             mIsImsServiceUp = true;
+
+                            /* set ImsManager */
+                            mSmsDispatchersController.setImsManager(mImsManager);
                         }
                     }
 
@@ -309,6 +366,9 @@
                         synchronized (mLock) {
                             mImsManager = null;
                             mIsImsServiceUp = false;
+
+                            /* unset ImsManager */
+                            mSmsDispatchersController.setImsManager(null);
                         }
                     }
                 }, this::post);
@@ -394,6 +454,81 @@
     }
 
     @Override
+    public int getMaxSmsRetryCount() {
+        int retryCount = MAX_SEND_RETRIES;
+        CarrierConfigManager mConfigManager;
+
+        mConfigManager = (CarrierConfigManager)  mContext.getSystemService(
+                Context.CARRIER_CONFIG_SERVICE);
+
+        if (mConfigManager != null) {
+            PersistableBundle carrierConfig = mConfigManager.getConfigForSubId(
+                    getSubId());
+
+            if (carrierConfig != null) {
+                retryCount = carrierConfig.getInt(
+                        CarrierConfigManager.ImsSms.KEY_SMS_MAX_RETRY_COUNT_INT);
+            }
+        }
+
+        Rlog.d(TAG, "Retry Count: " + retryCount);
+
+        return retryCount;
+    }
+
+    /**
+     * Returns the number of times SMS can be sent over IMS
+     *
+     * @return  retry count over Ims from  carrier configuration
+     */
+    @VisibleForTesting
+    public int getMaxRetryCountOverIms() {
+        int retryCountOverIms = MAX_SEND_RETRIES_OVER_IMS;
+        CarrierConfigManager mConfigManager;
+
+        mConfigManager = (CarrierConfigManager) mContext.getSystemService(Context
+                                                        .CARRIER_CONFIG_SERVICE);
+
+        if (mConfigManager != null) {
+            PersistableBundle carrierConfig = mConfigManager.getConfigForSubId(
+                    getSubId());
+
+
+            if (carrierConfig != null) {
+                retryCountOverIms = carrierConfig.getInt(
+                        CarrierConfigManager.ImsSms.KEY_SMS_MAX_RETRY_OVER_IMS_COUNT_INT);
+            }
+        }
+
+        Rlog.d(TAG, "Retry Count Over Ims: " + retryCountOverIms);
+
+        return retryCountOverIms;
+    }
+
+    @Override
+    public int getSmsRetryDelayValue() {
+        int retryDelay = SEND_RETRY_DELAY;
+        CarrierConfigManager mConfigManager;
+
+        mConfigManager = (CarrierConfigManager)  mContext.getSystemService(
+                Context.CARRIER_CONFIG_SERVICE);
+
+        if (mConfigManager != null) {
+            PersistableBundle carrierConfig = mConfigManager.getConfigForSubId(
+                    getSubId());
+
+            if (carrierConfig != null) {
+                retryDelay = carrierConfig.getInt(
+                        CarrierConfigManager.ImsSms.KEY_SMS_OVER_IMS_SEND_RETRY_DELAY_MILLIS_INT);
+            }
+        }
+
+        Rlog.d(TAG, "Retry delay: " + retryDelay);
+
+        return retryDelay;
+    }
+
+    @Override
     protected boolean shouldBlockSmsForEcbm() {
         // We should not block outgoing SMS during ECM on IMS. It only applies to outgoing CDMA
         // SMS.
@@ -435,6 +570,24 @@
         return SMSDispatcherUtil.calculateLength(isCdmaMo(), messageBody, use7bitOnly);
     }
 
+    /**
+     * Send the Memory Available Event to the ImsService
+     */
+    public void onMemoryAvailable() {
+        logd("onMemoryAvailable ");
+        int token = mNextToken.incrementAndGet();
+        try {
+            logd("onMemoryAvailable: token = " + token);
+            mMemoryAvailableNotifierList.add(token);
+            getImsManager().onMemoryAvailable(token);
+        } catch (ImsException e) {
+            loge("onMemoryAvailable failed: " + e.getMessage());
+            if (mMemoryAvailableNotifierList.contains(token)) {
+                mMemoryAvailableNotifierList.remove(Integer.valueOf(token));
+            }
+        }
+    }
+
     @Override
     public void sendSms(SmsTracker tracker) {
         logd("sendSms: "
diff --git a/src/java/com/android/internal/telephony/InboundSmsHandler.java b/src/java/com/android/internal/telephony/InboundSmsHandler.java
index 8f43dce..9166719 100644
--- a/src/java/com/android/internal/telephony/InboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/InboundSmsHandler.java
@@ -45,7 +45,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.database.SQLException;
@@ -53,11 +52,10 @@
 import android.os.AsyncResult;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.PowerWhitelistManager;
-import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.StorageManager;
@@ -185,6 +183,7 @@
     /** BroadcastReceiver timed out waiting for an intent */
     public static final int EVENT_RECEIVER_TIMEOUT = 9;
 
+
     /** Wakelock release delay when returning to idle state. */
     private static final int WAKELOCK_TIMEOUT = 3000;
 
@@ -289,8 +288,8 @@
      * @param storageMonitor the SmsStorageMonitor to check for storage availability
      */
     protected InboundSmsHandler(String name, Context context, SmsStorageMonitor storageMonitor,
-            Phone phone) {
-        super(name);
+            Phone phone, Looper looper) {
+        super(name, looper);
 
         mContext = context;
         mStorageMonitor = storageMonitor;
@@ -501,7 +500,6 @@
                 case EVENT_RETURN_TO_IDLE:
                     // already in idle state; ignore
                     return HANDLED;
-
                 case EVENT_BROADCAST_COMPLETE:
                 case EVENT_START_ACCEPTING_SMS:
                 default:
@@ -540,7 +538,8 @@
 
                 case EVENT_INJECT_SMS:
                     // handle new injected SMS
-                    handleInjectSms((AsyncResult) msg.obj, msg.arg1 == 1 /* isOverIms */);
+                    handleInjectSms((AsyncResult) msg.obj, msg.arg1 == 1 /* isOverIms */,
+                            msg.arg2 /* token */);
                     sendMessage(EVENT_RETURN_TO_IDLE);
                     return HANDLED;
 
@@ -663,7 +662,6 @@
             }
         }
     }
-
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private void handleNewSms(AsyncResult ar) {
         if (ar.exception != null) {
@@ -674,7 +672,7 @@
         int result;
         try {
             SmsMessage sms = (SmsMessage) ar.result;
-            result = dispatchMessage(sms.mWrappedSmsMessage, SOURCE_NOT_INJECTED);
+            result = dispatchMessage(sms.mWrappedSmsMessage, SOURCE_NOT_INJECTED, 0 /*unused*/);
         } catch (RuntimeException ex) {
             loge("Exception dispatching message", ex);
             result = RESULT_SMS_DISPATCH_FAILURE;
@@ -693,7 +691,7 @@
      * @param ar is the AsyncResult that has the SMS PDU to be injected.
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private void handleInjectSms(AsyncResult ar, boolean isOverIms) {
+    private void handleInjectSms(AsyncResult ar, boolean isOverIms, int token) {
         int result;
         SmsDispatchersController.SmsInjectionCallback callback = null;
         try {
@@ -705,7 +703,7 @@
             } else {
                 @SmsSource int smsSource =
                         isOverIms ? SOURCE_INJECTED_FROM_IMS : SOURCE_INJECTED_FROM_UNKNOWN;
-                result = dispatchMessage(sms.mWrappedSmsMessage, smsSource);
+                result = dispatchMessage(sms.mWrappedSmsMessage, smsSource, token);
             }
         } catch (RuntimeException ex) {
             loge("Exception dispatching message", ex);
@@ -726,7 +724,7 @@
      * @return a result code from {@link android.provider.Telephony.Sms.Intents},
      *  or {@link Activity#RESULT_OK} for delayed acknowledgment to SMSC
      */
-    private int dispatchMessage(SmsMessageBase smsb, @SmsSource int smsSource) {
+    private int dispatchMessage(SmsMessageBase smsb, @SmsSource int smsSource, int token) {
         // If sms is null, there was a parsing error.
         if (smsb == null) {
             loge("dispatchSmsMessage: message is null");
@@ -740,20 +738,7 @@
             return Intents.RESULT_SMS_HANDLED;
         }
 
-        // onlyCore indicates if the device is in cryptkeeper
-        boolean onlyCore = false;
-        try {
-            onlyCore = IPackageManager.Stub.asInterface(ServiceManager.getService("package"))
-                    .isOnlyCoreApps();
-        } catch (RemoteException e) {
-        }
-        if (onlyCore) {
-            // Device is unable to receive SMS in encrypted state
-            log("Received a short message in encrypted state. Rejecting.");
-            return Intents.RESULT_SMS_RECEIVED_WHILE_ENCRYPTED;
-        }
-
-        int result = dispatchMessageRadioSpecific(smsb, smsSource);
+        int result = dispatchMessageRadioSpecific(smsb, smsSource, token);
 
         // In case of error, add to metrics. This is not required in case of success, as the
         // data will be tracked when the message is processed (processMessagePart).
@@ -775,7 +760,7 @@
      *  or {@link Activity#RESULT_OK} for delayed acknowledgment to SMSC
      */
     protected abstract int dispatchMessageRadioSpecific(SmsMessageBase smsb,
-            @SmsSource int smsSource);
+            @SmsSource int smsSource, int token);
 
     /**
      * Send an acknowledge message to the SMSC.
@@ -875,7 +860,6 @@
      * <code>RESULT_SMS_DISPATCH_FAILURE</code><br>
      * <code>RESULT_SMS_NULL_PDU</code><br>
      * <code>RESULT_SMS_NULL_MESSAGE</code><br>
-     * <code>RESULT_SMS_RECEIVED_WHILE_ENCRYPTED</code><br>
      * <code>RESULT_SMS_DATABASE_ERROR</code><br>
      * <code>RESULT_SMS_INVALID_URI</code><br>
      */
@@ -1159,7 +1143,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private void showNewMessageNotification() {
         // Do not show the notification on non-FBE devices.
-        if (!StorageManager.isFileEncryptedNativeOrEmulated()) {
+        if (!StorageManager.isFileEncrypted()) {
             return;
         }
         log("Show new message notification.");
@@ -1453,12 +1437,14 @@
             intent.putExtra("messageId", messageId);
         }
 
+        UserHandle userHandle = null;
         if (destPort == -1) {
             intent.setAction(Intents.SMS_DELIVER_ACTION);
             // Direct the intent to only the default SMS app. If we can't find a default SMS app
             // then sent it to all broadcast receivers.
-            // We are deliberately delivering to the primary user's default SMS App.
-            ComponentName componentName = SmsApplication.getDefaultSmsApplication(mContext, true);
+            userHandle = TelephonyUtils.getSubscriptionUserHandle(mContext, subId);
+            ComponentName componentName = SmsApplication.getDefaultSmsApplicationAsUser(mContext,
+                    true, userHandle);
             if (componentName != null) {
                 // Deliver SMS message only to this receiver.
                 intent.setComponent(componentName);
@@ -1482,9 +1468,12 @@
             intent.setComponent(null);
         }
 
+        if (userHandle == null) {
+            userHandle = UserHandle.SYSTEM;
+        }
         Bundle options = handleSmsWhitelisting(intent.getComponent(), isClass0);
         dispatchIntent(intent, android.Manifest.permission.RECEIVE_SMS,
-                AppOpsManager.OPSTR_RECEIVE_SMS, options, resultReceiver, UserHandle.SYSTEM, subId);
+                AppOpsManager.OPSTR_RECEIVE_SMS, options, resultReceiver, userHandle, subId);
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/LocaleTracker.java b/src/java/com/android/internal/telephony/LocaleTracker.java
old mode 100755
new mode 100644
index 31d6686..de854fa
--- a/src/java/com/android/internal/telephony/LocaleTracker.java
+++ b/src/java/com/android/internal/telephony/LocaleTracker.java
@@ -30,6 +30,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.UserHandle;
 import android.sysprop.TelephonyProperties;
 import android.telephony.CellInfo;
 import android.telephony.ServiceState;
@@ -558,7 +559,7 @@
             intent.putExtra(TelephonyManager.EXTRA_LAST_KNOWN_NETWORK_COUNTRY,
                     getLastKnownCountryIso());
             SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
-            mPhone.getContext().sendBroadcast(intent);
+            mPhone.getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
         }
 
         // Pass the geographical country information to the telephony time zone detection code.
diff --git a/src/java/com/android/internal/telephony/MessagingIndication.java b/src/java/com/android/internal/telephony/MessagingIndication.java
index 96e74cc..5814e3d 100644
--- a/src/java/com/android/internal/telephony/MessagingIndication.java
+++ b/src/java/com/android/internal/telephony/MessagingIndication.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony;
 
+import static android.telephony.TelephonyManager.HAL_SERVICE_MESSAGING;
+
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_RUIM_SMS_STORAGE_FULL;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_CDMA_NEW_SMS;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_NEW_BROADCAST_SMS;
@@ -47,7 +49,7 @@
      */
     public void cdmaNewSms(int indicationType,
             android.hardware.radio.messaging.CdmaSmsMessage msg) {
-        mRil.processIndication(RIL.MESSAGING_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_MESSAGING, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_CDMA_NEW_SMS);
 
@@ -63,7 +65,7 @@
      * @param indicationType Type of radio indication
      */
     public void cdmaRuimSmsStorageFull(int indicationType) {
-        mRil.processIndication(RIL.MESSAGING_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_MESSAGING, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_CDMA_RUIM_SMS_STORAGE_FULL);
 
@@ -82,7 +84,7 @@
      *        BTS as coded in 3GPP 23.041 Section 9.4.2.2
      */
     public void newBroadcastSms(int indicationType, byte[] data) {
-        mRil.processIndication(RIL.MESSAGING_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_MESSAGING, indicationType);
 
         if (mRil.isLogOrTrace()) {
             mRil.unsljLogvRet(
@@ -101,7 +103,7 @@
      *        The PDU starts with the SMSC address per TS 27.005 (+CMT:)
      */
     public void newSms(int indicationType, byte[] pdu) {
-        mRil.processIndication(RIL.MESSAGING_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_MESSAGING, indicationType);
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_NEW_SMS);
 
         SmsMessageBase smsb = com.android.internal.telephony.gsm.SmsMessage.createFromPdu(pdu);
@@ -117,7 +119,7 @@
      * @param recordNumber Record number on the SIM
      */
     public void newSmsOnSim(int indicationType, int recordNumber) {
-        mRil.processIndication(RIL.MESSAGING_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_MESSAGING, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_NEW_SMS_ON_SIM);
 
@@ -133,7 +135,7 @@
      *        The PDU starts with the SMSC address per TS 27.005 (+CMT:)
      */
     public void newSmsStatusReport(int indicationType, byte[] pdu) {
-        mRil.processIndication(RIL.MESSAGING_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_MESSAGING, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT);
 
@@ -149,7 +151,7 @@
      * @param indicationType Type of radio indication
      */
     public void simSmsStorageFull(int indicationType) {
-        mRil.processIndication(RIL.MESSAGING_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_MESSAGING, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_SIM_SMS_STORAGE_FULL);
 
diff --git a/src/java/com/android/internal/telephony/MessagingResponse.java b/src/java/com/android/internal/telephony/MessagingResponse.java
index 3dc1d1a..19211e1 100644
--- a/src/java/com/android/internal/telephony/MessagingResponse.java
+++ b/src/java/com/android/internal/telephony/MessagingResponse.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony;
 
+import static android.telephony.TelephonyManager.HAL_SERVICE_MESSAGING;
+
 import android.hardware.radio.RadioError;
 import android.hardware.radio.RadioResponseInfo;
 import android.hardware.radio.messaging.IRadioMessagingResponse;
@@ -36,7 +38,7 @@
 
     private void responseSms(RadioResponseInfo responseInfo,
             android.hardware.radio.messaging.SendSmsResult sms) {
-        RILRequest rr = mRil.processResponse(RIL.MESSAGING_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_MESSAGING, responseInfo);
 
         if (rr != null) {
             long messageId = RIL.getOutgoingSmsMessageId(rr.mResult);
@@ -62,35 +64,35 @@
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void acknowledgeIncomingGsmSmsWithPduResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.MESSAGING_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_MESSAGING, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void acknowledgeLastIncomingCdmaSmsResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.MESSAGING_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_MESSAGING, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void acknowledgeLastIncomingGsmSmsResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.MESSAGING_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_MESSAGING, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void deleteSmsOnRuimResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.MESSAGING_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_MESSAGING, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void deleteSmsOnSimResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.MESSAGING_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_MESSAGING, mRil, responseInfo);
     }
 
     /**
@@ -99,7 +101,7 @@
      */
     public void getCdmaBroadcastConfigResponse(RadioResponseInfo responseInfo,
             android.hardware.radio.messaging.CdmaBroadcastSmsConfigInfo[] configs) {
-        RILRequest rr = mRil.processResponse(RIL.MESSAGING_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_MESSAGING, responseInfo);
 
         if (rr != null) {
             int[] ret;
@@ -148,7 +150,7 @@
      */
     public void getGsmBroadcastConfigResponse(RadioResponseInfo responseInfo,
             android.hardware.radio.messaging.GsmBroadcastSmsConfigInfo[] configs) {
-        RILRequest rr = mRil.processResponse(RIL.MESSAGING_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_MESSAGING, responseInfo);
 
         if (rr != null) {
             ArrayList<SmsBroadcastConfigInfo> ret = new ArrayList<>();
@@ -168,14 +170,14 @@
      * @param smsc Short Message Service Center address on the device
      */
     public void getSmscAddressResponse(RadioResponseInfo responseInfo, String smsc) {
-        RadioResponse.responseString(RIL.MESSAGING_SERVICE, mRil, responseInfo, smsc);
+        RadioResponse.responseString(HAL_SERVICE_MESSAGING, mRil, responseInfo, smsc);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void reportSmsMemoryStatusResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.MESSAGING_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_MESSAGING, mRil, responseInfo);
     }
 
     /**
@@ -227,35 +229,35 @@
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setCdmaBroadcastActivationResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.MESSAGING_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_MESSAGING, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setCdmaBroadcastConfigResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.MESSAGING_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_MESSAGING, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setGsmBroadcastActivationResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.MESSAGING_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_MESSAGING, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setGsmBroadcastConfigResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.MESSAGING_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_MESSAGING, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setSmscAddressResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.MESSAGING_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_MESSAGING, mRil, responseInfo);
     }
 
     /**
@@ -263,7 +265,7 @@
      * @param index record index where the CDMA SMS message is stored
      */
     public void writeSmsToRuimResponse(RadioResponseInfo responseInfo, int index) {
-        RadioResponse.responseInts(RIL.MESSAGING_SERVICE, mRil, responseInfo, index);
+        RadioResponse.responseInts(HAL_SERVICE_MESSAGING, mRil, responseInfo, index);
     }
 
     /**
@@ -271,7 +273,7 @@
      * @param index record index where the message is stored
      */
     public void writeSmsToSimResponse(RadioResponseInfo responseInfo, int index) {
-        RadioResponse.responseInts(RIL.MESSAGING_SERVICE, mRil, responseInfo, index);
+        RadioResponse.responseInts(HAL_SERVICE_MESSAGING, mRil, responseInfo, index);
     }
 
     @Override
diff --git a/src/java/com/android/internal/telephony/MissedIncomingCallSmsFilter.java b/src/java/com/android/internal/telephony/MissedIncomingCallSmsFilter.java
index 97495d6..dce65af 100644
--- a/src/java/com/android/internal/telephony/MissedIncomingCallSmsFilter.java
+++ b/src/java/com/android/internal/telephony/MissedIncomingCallSmsFilter.java
@@ -240,7 +240,7 @@
     private String[] splitCalls(String messageBody) {
         String[] messages = null;
         if (messageBody != null) {
-            messages = messageBody.split("\\n" + "\\n");
+            messages = messageBody.split("(\\n|\\s\\n)" + "(\\n|\\s\\n)");
             Rlog.d(TAG,
                     "splitTheMultipleCalls no of calls = " + ((messages != null) ? messages.length
                             : 0));
@@ -248,20 +248,6 @@
         return messages;
     }
 
-    // Create phone account. The logic is copied from PhoneUtils.makePstnPhoneAccountHandle.
-    private PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) {
-        SubscriptionManager subscriptionManager =
-                (SubscriptionManager) phone.getContext().getSystemService(
-                        Context.TELEPHONY_SUBSCRIPTION_SERVICE);
-        UserHandle userHandle = subscriptionManager.getSubscriptionUserHandle(phone.getSubId());
-        if (userHandle != null) {
-            return new PhoneAccountHandle(PSTN_CONNECTION_SERVICE_COMPONENT,
-                    String.valueOf(phone.getSubId()), userHandle);
-        }
-        return new PhoneAccountHandle(PSTN_CONNECTION_SERVICE_COMPONENT,
-                String.valueOf(phone.getSubId()));
-    }
-
     /**
      * Create the missed incoming call through TelecomManager.
      *
@@ -288,4 +274,18 @@
             tm.addNewIncomingCall(makePstnPhoneAccountHandle(mPhone), bundle);
         }
     }
-}
+
+    // Create phone account. The logic is copied from PhoneUtils.makePstnPhoneAccountHandle.
+    private PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) {
+        SubscriptionManager subscriptionManager =
+                (SubscriptionManager) phone.getContext().getSystemService(
+                        Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+        UserHandle userHandle = subscriptionManager.getSubscriptionUserHandle(phone.getSubId());
+        if (userHandle != null) {
+            return new PhoneAccountHandle(PSTN_CONNECTION_SERVICE_COMPONENT,
+                    String.valueOf(phone.getSubId()), userHandle);
+        }
+        return new PhoneAccountHandle(PSTN_CONNECTION_SERVICE_COMPONENT,
+                String.valueOf(phone.getSubId()));
+    }
+}
\ No newline at end of file
diff --git a/src/java/com/android/internal/telephony/MockModem.java b/src/java/com/android/internal/telephony/MockModem.java
index 4266a75..a20e748 100644
--- a/src/java/com/android/internal/telephony/MockModem.java
+++ b/src/java/com/android/internal/telephony/MockModem.java
@@ -16,6 +16,14 @@
 
 package com.android.internal.telephony;
 
+import static android.telephony.TelephonyManager.HAL_SERVICE_DATA;
+import static android.telephony.TelephonyManager.HAL_SERVICE_IMS;
+import static android.telephony.TelephonyManager.HAL_SERVICE_MESSAGING;
+import static android.telephony.TelephonyManager.HAL_SERVICE_MODEM;
+import static android.telephony.TelephonyManager.HAL_SERVICE_NETWORK;
+import static android.telephony.TelephonyManager.HAL_SERVICE_SIM;
+import static android.telephony.TelephonyManager.HAL_SERVICE_VOICE;
+
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -34,6 +42,7 @@
     private static final String BIND_IRADIODATA = "android.telephony.mockmodem.iradiodata";
     private static final String BIND_IRADIONETWORK = "android.telephony.mockmodem.iradionetwork";
     private static final String BIND_IRADIOVOICE = "android.telephony.mockmodem.iradiovoice";
+    private static final String BIND_IRADIOIMS = "android.telephony.mockmodem.iradioims";
     private static final String BIND_IRADIOCONFIG = "android.telephony.mockmodem.iradioconfig";
     private static final String PHONE_ID = "phone_id";
 
@@ -42,7 +51,7 @@
     static final int RADIOCONFIG_SERVICE = RIL.MAX_SERVICE_IDX + 1;
 
     static final int BINDER_RETRY_MILLIS = 3 * 100;
-    static final int BINDER_MAX_RETRY = 3;
+    static final int BINDER_MAX_RETRY = 10;
 
     private Context mContext;
     private String mServiceName;
@@ -54,6 +63,7 @@
     private IBinder mDataBinder;
     private IBinder mNetworkBinder;
     private IBinder mVoiceBinder;
+    private IBinder mImsBinder;
     private IBinder mConfigBinder;
     private ServiceConnection mModemServiceConnection;
     private ServiceConnection mSimServiceConnection;
@@ -61,9 +71,11 @@
     private ServiceConnection mDataServiceConnection;
     private ServiceConnection mNetworkServiceConnection;
     private ServiceConnection mVoiceServiceConnection;
+    private ServiceConnection mImsServiceConnection;
     private ServiceConnection mConfigServiceConnection;
 
     private byte mPhoneId;
+    private String mTag;
 
     MockModem(Context context, String serviceName) {
         this(context, serviceName, 0);
@@ -71,6 +83,7 @@
 
     MockModem(Context context, String serviceName, int phoneId) {
         mPhoneId = (byte) phoneId;
+        mTag = TAG + "-" + mPhoneId;
         mContext = context;
         String[] componentInfo = serviceName.split("/", 2);
         mPackageName = componentInfo[0];
@@ -86,20 +99,22 @@
 
         @Override
         public void onServiceConnected(ComponentName name, IBinder binder) {
-            Rlog.d(TAG, "IRadio " + getModuleName(mService) + "  - onServiceConnected");
+            Rlog.d(mTag, "IRadio " + getModuleName(mService) + "  - onServiceConnected");
 
-            if (mService == RIL.MODEM_SERVICE) {
+            if (mService == HAL_SERVICE_MODEM) {
                 mModemBinder = binder;
-            } else if (mService == RIL.SIM_SERVICE) {
+            } else if (mService == HAL_SERVICE_SIM) {
                 mSimBinder = binder;
-            } else if (mService == RIL.MESSAGING_SERVICE) {
+            } else if (mService == HAL_SERVICE_MESSAGING) {
                 mMessagingBinder = binder;
-            } else if (mService == RIL.DATA_SERVICE) {
+            } else if (mService == HAL_SERVICE_DATA) {
                 mDataBinder = binder;
-            } else if (mService == RIL.NETWORK_SERVICE) {
+            } else if (mService == HAL_SERVICE_NETWORK) {
                 mNetworkBinder = binder;
-            } else if (mService == RIL.VOICE_SERVICE) {
+            } else if (mService == HAL_SERVICE_VOICE) {
                 mVoiceBinder = binder;
+            } else if (mService == HAL_SERVICE_IMS) {
+                mImsBinder = binder;
             } else if (mService == RADIOCONFIG_SERVICE) {
                 mConfigBinder = binder;
             }
@@ -107,20 +122,22 @@
 
         @Override
         public void onServiceDisconnected(ComponentName name) {
-            Rlog.d(TAG, "IRadio " + getModuleName(mService) + "  - onServiceDisconnected");
+            Rlog.d(mTag, "IRadio " + getModuleName(mService) + "  - onServiceDisconnected");
 
-            if (mService == RIL.MODEM_SERVICE) {
+            if (mService == HAL_SERVICE_MODEM) {
                 mModemBinder = null;
-            } else if (mService == RIL.SIM_SERVICE) {
+            } else if (mService == HAL_SERVICE_SIM) {
                 mSimBinder = null;
-            } else if (mService == RIL.MESSAGING_SERVICE) {
+            } else if (mService == HAL_SERVICE_MESSAGING) {
                 mMessagingBinder = null;
-            } else if (mService == RIL.DATA_SERVICE) {
+            } else if (mService == HAL_SERVICE_DATA) {
                 mDataBinder = null;
-            } else if (mService == RIL.NETWORK_SERVICE) {
+            } else if (mService == HAL_SERVICE_NETWORK) {
                 mNetworkBinder = null;
-            } else if (mService == RIL.VOICE_SERVICE) {
+            } else if (mService == HAL_SERVICE_VOICE) {
                 mVoiceBinder = null;
+            } else if (mService == HAL_SERVICE_IMS) {
+                mImsBinder = null;
             } else if (mService == RADIOCONFIG_SERVICE) {
                 mConfigBinder = null;
             }
@@ -138,7 +155,7 @@
 
         Intent intent = new Intent();
         intent.setComponent(new ComponentName(mPackageName, mServiceName));
-        intent.setAction(actionName);
+        intent.setAction(actionName + phoneId);
         intent.putExtra(PHONE_ID, phoneId);
 
         status = mContext.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
@@ -148,18 +165,20 @@
     /** waitForBinder */
     public IBinder getServiceBinder(int service) {
         switch (service) {
-            case RIL.MODEM_SERVICE:
+            case HAL_SERVICE_MODEM:
                 return mModemBinder;
-            case RIL.SIM_SERVICE:
+            case HAL_SERVICE_SIM:
                 return mSimBinder;
-            case RIL.MESSAGING_SERVICE:
+            case HAL_SERVICE_MESSAGING:
                 return mMessagingBinder;
-            case RIL.DATA_SERVICE:
+            case HAL_SERVICE_DATA:
                 return mDataBinder;
-            case RIL.NETWORK_SERVICE:
+            case HAL_SERVICE_NETWORK:
                 return mNetworkBinder;
-            case RIL.VOICE_SERVICE:
+            case HAL_SERVICE_VOICE:
                 return mVoiceBinder;
+            case HAL_SERVICE_IMS:
+                return mImsBinder;
             case RADIOCONFIG_SERVICE:
                 return mConfigBinder;
             default:
@@ -183,95 +202,109 @@
                 boolean status =
                         bindModuleToMockModemService(BIND_IRADIOCONFIG, mConfigServiceConnection);
                 if (!status) {
-                    Rlog.d(TAG, "IRadio Config bind fail");
+                    Rlog.d(mTag, "IRadio Config bind fail");
                     mConfigServiceConnection = null;
                 }
             } else {
-                Rlog.d(TAG, "IRadio Config is bound");
+                Rlog.d(mTag, "IRadio Config is bound");
             }
-        } else if (service == RIL.MODEM_SERVICE) {
+        } else if (service == HAL_SERVICE_MODEM) {
             if (mModemBinder == null) {
-                mModemServiceConnection = new MockModemConnection(RIL.MODEM_SERVICE);
+                mModemServiceConnection = new MockModemConnection(HAL_SERVICE_MODEM);
 
                 boolean status =
                         bindModuleToMockModemService(
                                 mPhoneId, BIND_IRADIOMODEM, mModemServiceConnection);
                 if (!status) {
-                    Rlog.d(TAG, "IRadio Modem bind fail");
+                    Rlog.d(mTag, "IRadio Modem bind fail");
                     mModemServiceConnection = null;
                 }
             } else {
-                Rlog.d(TAG, "IRadio Modem is bound");
+                Rlog.d(mTag, "IRadio Modem is bound");
             }
-        } else if (service == RIL.SIM_SERVICE) {
+        } else if (service == HAL_SERVICE_SIM) {
             if (mSimBinder == null) {
-                mSimServiceConnection = new MockModemConnection(RIL.SIM_SERVICE);
+                mSimServiceConnection = new MockModemConnection(HAL_SERVICE_SIM);
 
                 boolean status =
                         bindModuleToMockModemService(
                                 mPhoneId, BIND_IRADIOSIM, mSimServiceConnection);
                 if (!status) {
-                    Rlog.d(TAG, "IRadio Sim bind fail");
+                    Rlog.d(mTag, "IRadio Sim bind fail");
                     mSimServiceConnection = null;
                 }
             } else {
-                Rlog.d(TAG, "IRadio Sim is bound");
+                Rlog.d(mTag, "IRadio Sim is bound");
             }
-        } else if (service == RIL.MESSAGING_SERVICE) {
+        } else if (service == HAL_SERVICE_MESSAGING) {
             if (mMessagingBinder == null) {
-                mMessagingServiceConnection = new MockModemConnection(RIL.MESSAGING_SERVICE);
+                mMessagingServiceConnection = new MockModemConnection(HAL_SERVICE_MESSAGING);
 
                 boolean status =
                         bindModuleToMockModemService(
                                 mPhoneId, BIND_IRADIOMESSAGING, mMessagingServiceConnection);
                 if (!status) {
-                    Rlog.d(TAG, "IRadio Messaging bind fail");
+                    Rlog.d(mTag, "IRadio Messaging bind fail");
                     mMessagingServiceConnection = null;
                 }
             } else {
-                Rlog.d(TAG, "IRadio Messaging is bound");
+                Rlog.d(mTag, "IRadio Messaging is bound");
             }
-        } else if (service == RIL.DATA_SERVICE) {
+        } else if (service == HAL_SERVICE_DATA) {
             if (mDataBinder == null) {
-                mDataServiceConnection = new MockModemConnection(RIL.DATA_SERVICE);
+                mDataServiceConnection = new MockModemConnection(HAL_SERVICE_DATA);
 
                 boolean status =
                         bindModuleToMockModemService(
                                 mPhoneId, BIND_IRADIODATA, mDataServiceConnection);
                 if (!status) {
-                    Rlog.d(TAG, "IRadio Data bind fail");
+                    Rlog.d(mTag, "IRadio Data bind fail");
                     mDataServiceConnection = null;
                 }
             } else {
-                Rlog.d(TAG, "IRadio Data is bound");
+                Rlog.d(mTag, "IRadio Data is bound");
             }
-        } else if (service == RIL.NETWORK_SERVICE) {
+        } else if (service == HAL_SERVICE_NETWORK) {
             if (mNetworkBinder == null) {
-                mNetworkServiceConnection = new MockModemConnection(RIL.NETWORK_SERVICE);
+                mNetworkServiceConnection = new MockModemConnection(HAL_SERVICE_NETWORK);
 
                 boolean status =
                         bindModuleToMockModemService(
                                 mPhoneId, BIND_IRADIONETWORK, mNetworkServiceConnection);
                 if (!status) {
-                    Rlog.d(TAG, "IRadio Network bind fail");
+                    Rlog.d(mTag, "IRadio Network bind fail");
                     mNetworkServiceConnection = null;
                 }
             } else {
-                Rlog.d(TAG, "IRadio Network is bound");
+                Rlog.d(mTag, "IRadio Network is bound");
             }
-        } else if (service == RIL.VOICE_SERVICE) {
+        } else if (service == HAL_SERVICE_VOICE) {
             if (mVoiceBinder == null) {
-                mVoiceServiceConnection = new MockModemConnection(RIL.VOICE_SERVICE);
+                mVoiceServiceConnection = new MockModemConnection(HAL_SERVICE_VOICE);
 
                 boolean status =
                         bindModuleToMockModemService(
                                 mPhoneId, BIND_IRADIOVOICE, mVoiceServiceConnection);
                 if (!status) {
-                    Rlog.d(TAG, "IRadio Voice bind fail");
+                    Rlog.d(mTag, "IRadio Voice bind fail");
                     mVoiceServiceConnection = null;
                 }
             } else {
-                Rlog.d(TAG, "IRadio Voice is bound");
+                Rlog.d(mTag, "IRadio Voice is bound");
+            }
+        } else if (service == HAL_SERVICE_IMS) {
+            if (mImsBinder == null) {
+                mImsServiceConnection = new MockModemConnection(HAL_SERVICE_IMS);
+
+                boolean status =
+                        bindModuleToMockModemService(
+                                mPhoneId, BIND_IRADIOIMS, mImsServiceConnection);
+                if (!status) {
+                    Rlog.d(TAG, "IRadio Ims bind fail");
+                    mImsServiceConnection = null;
+                }
+            } else {
+                Rlog.d(TAG, "IRadio Ims is bound");
             }
         }
     }
@@ -284,49 +317,56 @@
                 mContext.unbindService(mConfigServiceConnection);
                 mConfigServiceConnection = null;
                 mConfigBinder = null;
-                Rlog.d(TAG, "unbind IRadio Config");
+                Rlog.d(mTag, "unbind IRadio Config");
             }
-        } else if (service == RIL.MODEM_SERVICE) {
+        } else if (service == HAL_SERVICE_MODEM) {
             if (mModemServiceConnection != null) {
                 mContext.unbindService(mModemServiceConnection);
                 mModemServiceConnection = null;
                 mModemBinder = null;
-                Rlog.d(TAG, "unbind IRadio Modem");
+                Rlog.d(mTag, "unbind IRadio Modem");
             }
-        } else if (service == RIL.SIM_SERVICE) {
+        } else if (service == HAL_SERVICE_SIM) {
             if (mSimServiceConnection != null) {
                 mContext.unbindService(mSimServiceConnection);
                 mSimServiceConnection = null;
                 mSimBinder = null;
-                Rlog.d(TAG, "unbind IRadio Sim");
+                Rlog.d(mTag, "unbind IRadio Sim");
             }
-        } else if (service == RIL.MESSAGING_SERVICE) {
+        } else if (service == HAL_SERVICE_MESSAGING) {
             if (mMessagingServiceConnection != null) {
                 mContext.unbindService(mMessagingServiceConnection);
                 mMessagingServiceConnection = null;
                 mMessagingBinder = null;
-                Rlog.d(TAG, "unbind IRadio Messaging");
+                Rlog.d(mTag, "unbind IRadio Messaging");
             }
-        } else if (service == RIL.DATA_SERVICE) {
+        } else if (service == HAL_SERVICE_DATA) {
             if (mDataServiceConnection != null) {
                 mContext.unbindService(mDataServiceConnection);
                 mDataServiceConnection = null;
                 mDataBinder = null;
-                Rlog.d(TAG, "unbind IRadio Data");
+                Rlog.d(mTag, "unbind IRadio Data");
             }
-        } else if (service == RIL.NETWORK_SERVICE) {
+        } else if (service == HAL_SERVICE_NETWORK) {
             if (mNetworkServiceConnection != null) {
                 mContext.unbindService(mNetworkServiceConnection);
                 mNetworkServiceConnection = null;
                 mNetworkBinder = null;
-                Rlog.d(TAG, "unbind IRadio Network");
+                Rlog.d(mTag, "unbind IRadio Network");
             }
-        } else if (service == RIL.VOICE_SERVICE) {
+        } else if (service == HAL_SERVICE_VOICE) {
             if (mVoiceServiceConnection != null) {
                 mContext.unbindService(mVoiceServiceConnection);
                 mVoiceServiceConnection = null;
                 mVoiceBinder = null;
-                Rlog.d(TAG, "unbind IRadio Voice");
+                Rlog.d(mTag, "unbind IRadio Voice");
+            }
+        } else if (service == HAL_SERVICE_IMS) {
+            if (mImsServiceConnection != null) {
+                mContext.unbindService(mImsServiceConnection);
+                mImsServiceConnection = null;
+                mImsBinder = null;
+                Rlog.d(TAG, "unbind IRadio Ims");
             }
         }
     }
@@ -337,18 +377,20 @@
 
     private String getModuleName(int service) {
         switch (service) {
-            case RIL.MODEM_SERVICE:
+            case HAL_SERVICE_MODEM:
                 return "modem";
-            case RIL.SIM_SERVICE:
+            case HAL_SERVICE_SIM:
                 return "sim";
-            case RIL.MESSAGING_SERVICE:
+            case HAL_SERVICE_MESSAGING:
                 return "messaging";
-            case RIL.DATA_SERVICE:
+            case HAL_SERVICE_DATA:
                 return "data";
-            case RIL.NETWORK_SERVICE:
+            case HAL_SERVICE_NETWORK:
                 return "network";
-            case RIL.VOICE_SERVICE:
+            case HAL_SERVICE_VOICE:
                 return "voice";
+            case HAL_SERVICE_IMS:
+                return "ims";
             case RADIOCONFIG_SERVICE:
                 return "config";
             default:
diff --git a/src/java/com/android/internal/telephony/ModemIndication.java b/src/java/com/android/internal/telephony/ModemIndication.java
index d05e0f6..0ee40bb 100644
--- a/src/java/com/android/internal/telephony/ModemIndication.java
+++ b/src/java/com/android/internal/telephony/ModemIndication.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony;
 
+import static android.telephony.TelephonyManager.HAL_SERVICE_MODEM;
+
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_HARDWARE_CONFIG_CHANGED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_MODEM_RESTART;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RADIO_CAPABILITY;
@@ -44,7 +46,7 @@
      */
     public void hardwareConfigChanged(int indicationType,
             android.hardware.radio.modem.HardwareConfig[] configs) {
-        mRil.processIndication(RIL.MODEM_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_MODEM, indicationType);
 
         ArrayList<HardwareConfig> response = RILUtils.convertHalHardwareConfigList(configs);
 
@@ -62,7 +64,7 @@
      *        restart" that explains the cause of the modem restart
      */
     public void modemReset(int indicationType, String reason) {
-        mRil.processIndication(RIL.MODEM_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_MODEM, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_MODEM_RESTART, reason);
 
@@ -78,7 +80,7 @@
      */
     public void radioCapabilityIndication(int indicationType,
             android.hardware.radio.modem.RadioCapability radioCapability) {
-        mRil.processIndication(RIL.MODEM_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_MODEM, indicationType);
 
         RadioCapability response = RILUtils.convertHalRadioCapability(radioCapability, mRil);
 
@@ -94,7 +96,7 @@
      * @param radioState Current radio state
      */
     public void radioStateChanged(int indicationType, int radioState) {
-        mRil.processIndication(RIL.MODEM_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_MODEM, indicationType);
 
         int state = RILUtils.convertHalRadioState(radioState);
         if (mRil.isLogOrTrace()) {
@@ -110,7 +112,7 @@
      * @param indicationType Type of radio indication
      */
     public void rilConnected(int indicationType) {
-        mRil.processIndication(RIL.MODEM_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_MODEM, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RIL_CONNECTED);
 
diff --git a/src/java/com/android/internal/telephony/ModemResponse.java b/src/java/com/android/internal/telephony/ModemResponse.java
index 6e44ddc..bd04d16 100644
--- a/src/java/com/android/internal/telephony/ModemResponse.java
+++ b/src/java/com/android/internal/telephony/ModemResponse.java
@@ -16,9 +16,12 @@
 
 package com.android.internal.telephony;
 
+import static android.telephony.TelephonyManager.HAL_SERVICE_MODEM;
+
 import android.hardware.radio.RadioError;
 import android.hardware.radio.RadioResponseInfo;
 import android.hardware.radio.modem.IRadioModemResponse;
+import android.hardware.radio.modem.ImeiInfo;
 import android.os.SystemClock;
 import android.telephony.ActivityStatsTechSpecificInfo;
 import android.telephony.AnomalyReporter;
@@ -51,7 +54,7 @@
      * @param responseInfo Response info struct containing response type, serial number and error.
      */
     public void enableModemResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.MODEM_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_MODEM, mRil, responseInfo);
     }
 
     /**
@@ -59,7 +62,7 @@
      * @param version String containing version string for log reporting
      */
     public void getBasebandVersionResponse(RadioResponseInfo responseInfo, String version) {
-        RadioResponse.responseString(RIL.MODEM_SERVICE, mRil, responseInfo, version);
+        RadioResponse.responseString(HAL_SERVICE_MODEM, mRil, responseInfo, version);
     }
 
     /**
@@ -72,7 +75,21 @@
     public void getDeviceIdentityResponse(RadioResponseInfo responseInfo, String imei,
             String imeisv, String esn, String meid) {
         RadioResponse.responseStrings(
-                RIL.MODEM_SERVICE, mRil, responseInfo, imei, imeisv, esn, meid);
+                HAL_SERVICE_MODEM, mRil, responseInfo, imei, imeisv, esn, meid);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param imeiInfo object containing ImeiType, device IMEI and IMEISV
+     */
+    public void getImeiResponse(RadioResponseInfo responseInfo, ImeiInfo imeiInfo) {
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_MODEM, responseInfo);
+        if (rr != null) {
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, imeiInfo);
+            }
+            mRil.processResponseDone(rr, responseInfo, imeiInfo);
+        }
     }
 
     /**
@@ -81,7 +98,7 @@
      */
     public void getHardwareConfigResponse(RadioResponseInfo responseInfo,
             android.hardware.radio.modem.HardwareConfig[] config) {
-        RILRequest rr = mRil.processResponse(RIL.MODEM_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_MODEM, responseInfo);
 
         if (rr != null) {
             ArrayList<HardwareConfig> ret = RILUtils.convertHalHardwareConfigList(config);
@@ -98,7 +115,7 @@
      */
     public void getModemActivityInfoResponse(RadioResponseInfo responseInfo,
             android.hardware.radio.modem.ActivityStatsInfo activityInfo) {
-        RILRequest rr = mRil.processResponse(RIL.MODEM_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_MODEM, responseInfo);
 
         if (rr != null) {
             ModemActivityInfo ret = null;
@@ -141,7 +158,7 @@
      * @param isEnabled whether the modem stack is enabled.
      */
     public void getModemStackStatusResponse(RadioResponseInfo responseInfo, boolean isEnabled) {
-        RILRequest rr = mRil.processResponse(RIL.MODEM_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_MODEM, responseInfo);
 
         if (rr != null) {
             if (responseInfo.error == RadioError.NONE) {
@@ -157,7 +174,7 @@
      */
     public void getRadioCapabilityResponse(RadioResponseInfo responseInfo,
             android.hardware.radio.modem.RadioCapability radioCapability) {
-        RILRequest rr = mRil.processResponse(RIL.MODEM_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_MODEM, responseInfo);
 
         if (rr != null) {
             RadioCapability ret = RILUtils.convertHalRadioCapability(radioCapability, mRil);
@@ -179,42 +196,42 @@
      * @param result String containing the contents of the NV item
      */
     public void nvReadItemResponse(RadioResponseInfo responseInfo, String result) {
-        RadioResponse.responseString(RIL.MODEM_SERVICE, mRil, responseInfo, result);
+        RadioResponse.responseString(HAL_SERVICE_MODEM, mRil, responseInfo, result);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void nvResetConfigResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.MODEM_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_MODEM, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void nvWriteCdmaPrlResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.MODEM_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_MODEM, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void nvWriteItemResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.MODEM_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_MODEM, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void requestShutdownResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.MODEM_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_MODEM, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void sendDeviceStateResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.MODEM_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_MODEM, mRil, responseInfo);
     }
 
     /**
@@ -223,7 +240,7 @@
      */
     public void setRadioCapabilityResponse(RadioResponseInfo responseInfo,
             android.hardware.radio.modem.RadioCapability radioCapability) {
-        RILRequest rr = mRil.processResponse(RIL.MODEM_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_MODEM, responseInfo);
 
         if (rr != null) {
             RadioCapability ret = RILUtils.convertHalRadioCapability(radioCapability, mRil);
@@ -238,7 +255,7 @@
      * @param responseInfo Response info struct containing response type, serial no. and error.
      */
     public void setRadioPowerResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.MODEM_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_MODEM, mRil, responseInfo);
         mRil.mLastRadioPowerResult = responseInfo.error;
         if (responseInfo.error == RadioError.RF_HARDWARE_ISSUE) {
             AnomalyReporter.reportAnomaly(
diff --git a/src/java/com/android/internal/telephony/MultiSimSettingController.java b/src/java/com/android/internal/telephony/MultiSimSettingController.java
index 7e87b5f..0acae4b 100644
--- a/src/java/com/android/internal/telephony/MultiSimSettingController.java
+++ b/src/java/com/android/internal/telephony/MultiSimSettingController.java
@@ -120,7 +120,6 @@
     private static final int PRIMARY_SUB_REMOVED_IN_GROUP       = 7;
 
     protected final Context mContext;
-    protected final SubscriptionController mSubController;
     private final SubscriptionManagerService mSubscriptionManagerService;
 
     // Keep a record of active primary (non-opportunistic) subscription list.
@@ -129,12 +128,7 @@
     /** The singleton instance. */
     protected static MultiSimSettingController sInstance = null;
 
-    // This will be set true when handling EVENT_ALL_SUBSCRIPTIONS_LOADED. The reason of keeping
-    // a local variable instead of calling SubscriptionInfoUpdater#isSubInfoInitialized is, there
-    // might be a race condition that we receive EVENT_SUBSCRIPTION_INFO_CHANGED first, then
-    // EVENT_ALL_SUBSCRIPTIONS_LOADED. And calling SubscriptionInfoUpdater#isSubInfoInitialized
-    // will make us handle EVENT_SUBSCRIPTION_INFO_CHANGED unexpectedly and causing us to believe
-    // the SIMs are newly inserted instead of being initialized.
+    // This will be set true when handling EVENT_ALL_SUBSCRIPTIONS_LOADED.
     private boolean mSubInfoInitialized = false;
 
     // mInitialHandling is to make sure we don't always ask user to re-select data SIM after reboot.
@@ -206,10 +200,10 @@
     /**
      * Init instance of MultiSimSettingController.
      */
-    public static MultiSimSettingController init(Context context, SubscriptionController sc) {
+    public static MultiSimSettingController init(Context context) {
         synchronized (MultiSimSettingController.class) {
             if (sInstance == null) {
-                sInstance = new MultiSimSettingController(context, sc);
+                sInstance = new MultiSimSettingController(context);
             } else {
                 Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
             }
@@ -218,9 +212,8 @@
     }
 
     @VisibleForTesting
-    public MultiSimSettingController(Context context, SubscriptionController sc) {
+    public MultiSimSettingController(Context context) {
         mContext = context;
-        mSubController = sc;
         mSubscriptionManagerService = SubscriptionManagerService.getInstance();
 
         // Initialize mCarrierConfigLoadedSubIds and register to listen to carrier config change.
@@ -346,26 +339,15 @@
         // Make sure MOBILE_DATA of subscriptions in same group are synced.
         setUserDataEnabledForGroup(subId, enable);
 
-        SubscriptionInfo subInfo = null;
-        int defaultDataSubId;
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            subInfo = mSubscriptionManagerService.getSubscriptionInfo(subId);
-            defaultDataSubId = mSubscriptionManagerService.getDefaultDataSubId();
-        } else {
-            subInfo = mSubController.getSubscriptionInfo(subId);
-            defaultDataSubId = mSubController.getDefaultDataSubId();
-        }
+        SubscriptionInfo subInfo = mSubscriptionManagerService.getSubscriptionInfo(subId);
+        int defaultDataSubId = mSubscriptionManagerService.getDefaultDataSubId();
 
         // If user is enabling a non-default non-opportunistic subscription, make it default.
         if (defaultDataSubId != subId && subInfo != null && !subInfo.isOpportunistic() && enable
                 && subInfo.isActive() && setDefaultData) {
             android.provider.Settings.Global.putInt(mContext.getContentResolver(),
                     SETTING_USER_PREF_DATA_SUB, subId);
-            if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-                mSubscriptionManagerService.setDefaultDataSubId(subId);
-            } else {
-                mSubController.setDefaultDataSubId(subId);
-            }
+            mSubscriptionManagerService.setDefaultDataSubId(subId);
         }
     }
 
@@ -376,13 +358,8 @@
         if (DBG) log("onRoamingDataEnabled");
         setRoamingDataEnabledForGroup(subId, enable);
 
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            // Also inform SubscriptionController as it keeps another copy of user setting.
-            mSubscriptionManagerService.setDataRoaming(enable ? 1 : 0, subId);
-        } else {
-            // Also inform SubscriptionController as it keeps another copy of user setting.
-            mSubController.setDataRoaming(enable ? 1 : 0, subId);
-        }
+        // Also inform SubscriptionManagerService as it keeps another copy of user setting.
+        mSubscriptionManagerService.setDataRoaming(enable ? 1 : 0, subId);
     }
 
     /**
@@ -457,12 +434,7 @@
      */
     @VisibleForTesting
     public boolean isCarrierConfigLoadedForAllSub() {
-        int[] activeSubIds;
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            activeSubIds = mSubscriptionManagerService.getActiveSubIdList(false);
-        } else {
-            activeSubIds = mSubController.getActiveSubIdList(false);
-        }
+        int[] activeSubIds = mSubscriptionManagerService.getActiveSubIdList(false);
         for (int activeSubId : activeSubIds) {
             boolean isLoaded = false;
             for (int configLoadedSub : mCarrierConfigLoadedSubIds) {
@@ -529,17 +501,9 @@
     private void onSubscriptionGroupChanged(ParcelUuid groupUuid) {
         if (DBG) log("onSubscriptionGroupChanged");
 
-        List<SubscriptionInfo> infoList;
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            infoList = mSubscriptionManagerService.getSubscriptionsInGroup(
-                    groupUuid, mContext.getOpPackageName(), mContext.getAttributionTag());
-            if (infoList == null || infoList.isEmpty()) return;
-
-        } else {
-            infoList = mSubController.getSubscriptionsInGroup(
-                    groupUuid, mContext.getOpPackageName(), mContext.getAttributionTag());
-            if (infoList == null || infoList.isEmpty()) return;
-        }
+        List<SubscriptionInfo> infoList = mSubscriptionManagerService.getSubscriptionsInGroup(
+                groupUuid, mContext.getOpPackageName(), mContext.getAttributionTag());
+        if (infoList == null || infoList.isEmpty()) return;
 
         // Get a reference subscription to copy settings from.
         // TODO: the reference sub should be passed in from external caller.
@@ -564,14 +528,9 @@
                     mContext, Settings.Global.MOBILE_DATA, INVALID_SUBSCRIPTION_ID, enable);
         }
         boolean setDefaultData = true;
-        List<SubscriptionInfo> activeSubList;
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            activeSubList = mSubscriptionManagerService.getActiveSubscriptionInfoList(
-                    mContext.getOpPackageName(), mContext.getAttributionTag());
-        } else {
-            activeSubList = mSubController.getActiveSubscriptionInfoList(
-                    mContext.getOpPackageName(), mContext.getAttributionTag());
-        }
+        List<SubscriptionInfo> activeSubList = mSubscriptionManagerService
+                .getActiveSubscriptionInfoList(mContext.getOpPackageName(),
+                        mContext.getAttributionTag());
         for (SubscriptionInfo activeInfo : activeSubList) {
             if (!(groupUuid.equals(activeInfo.getGroupUuid()))) {
                 // Do not set refSubId as defaultDataSubId if there are other active
@@ -594,12 +553,7 @@
             onRoamingDataEnabled(refSubId, enable);
         }
 
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            mSubscriptionManagerService.syncGroupedSetting(refSubId);
-        } else {
-            // Sync settings in subscription database..
-            mSubController.syncGroupedSetting(refSubId);
-        }
+        mSubscriptionManagerService.syncGroupedSetting(refSubId);
     }
 
     /**
@@ -621,34 +575,20 @@
 
         if (!isReadyToReevaluate()) return;
 
-        List<SubscriptionInfo> activeSubInfos;
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            activeSubInfos = mSubscriptionManagerService.getActiveSubscriptionInfoList(
-                    mContext.getOpPackageName(), mContext.getAttributionTag());
+        List<SubscriptionInfo> activeSubInfos = mSubscriptionManagerService
+                .getActiveSubscriptionInfoList(mContext.getOpPackageName(),
+                        mContext.getAttributionTag());
 
-            if (ArrayUtils.isEmpty(activeSubInfos)) {
-                mPrimarySubList.clear();
-                if (DBG) log("updateDefaults: No active sub. Setting default to INVALID sub.");
-                mSubscriptionManagerService.setDefaultDataSubId(
-                        SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-                mSubscriptionManagerService.setDefaultVoiceSubId(
-                        SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-                mSubscriptionManagerService.setDefaultSmsSubId(
-                        SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-                return;
-            }
-        } else {
-            activeSubInfos = mSubController.getActiveSubscriptionInfoList(
-                    mContext.getOpPackageName(), mContext.getAttributionTag());
-
-            if (ArrayUtils.isEmpty(activeSubInfos)) {
-                mPrimarySubList.clear();
-                if (DBG) log("updateDefaultValues: No active sub. Setting default to INVALID sub.");
-                mSubController.setDefaultDataSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-                mSubController.setDefaultVoiceSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-                mSubController.setDefaultSmsSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-                return;
-            }
+        if (ArrayUtils.isEmpty(activeSubInfos)) {
+            mPrimarySubList.clear();
+            if (DBG) log("updateDefaults: No active sub. Setting default to INVALID sub.");
+            mSubscriptionManagerService.setDefaultDataSubId(
+                    SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+            mSubscriptionManagerService.setDefaultVoiceSubId(
+                    SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+            mSubscriptionManagerService.setDefaultSmsSubId(
+                    SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+            return;
         }
 
         int change = updatePrimarySubListAndGetChangeType(activeSubInfos);
@@ -665,15 +605,9 @@
                 .getActiveModemCount() == 1)) {
             int subId = mPrimarySubList.get(0);
             if (DBG) log("updateDefaultValues: to only primary sub " + subId);
-            if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-                mSubscriptionManagerService.setDefaultDataSubId(subId);
-                mSubscriptionManagerService.setDefaultVoiceSubId(subId);
-                mSubscriptionManagerService.setDefaultSmsSubId(subId);
-            } else {
-                mSubController.setDefaultDataSubId(subId);
-                mSubController.setDefaultVoiceSubId(subId);
-                mSubController.setDefaultSmsSubId(subId);
-            }
+            mSubscriptionManagerService.setDefaultDataSubId(subId);
+            mSubscriptionManagerService.setDefaultVoiceSubId(subId);
+            mSubscriptionManagerService.setDefaultSmsSubId(subId);
             sendDefaultSubConfirmedNotification(subId);
             return;
         }
@@ -682,45 +616,24 @@
 
         boolean dataSelected, voiceSelected, smsSelected;
 
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            // Update default data subscription.
-            if (DBG) log("updateDefaultValues: Update default data subscription");
-            dataSelected = updateDefaultValue(mPrimarySubList,
-                    mSubscriptionManagerService.getDefaultDataSubId(),
-                    mSubscriptionManagerService::setDefaultDataSubId);
+        // 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);
+        // 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);
-        } else {
-            // Update default data subscription.
-            if (DBG) log("updateDefaultValues: Update default data subscription");
-            dataSelected = updateDefaultValue(mPrimarySubList,
-                    mSubController.getDefaultDataSubId(),
-                    mSubController::setDefaultDataSubId);
-
-            // Update default voice subscription.
-            if (DBG) log("updateDefaultValues: Update default voice subscription");
-            voiceSelected = updateDefaultValue(mPrimarySubList,
-                    mSubController.getDefaultVoiceSubId(),
-                    mSubController::setDefaultVoiceSubId);
-
-            // Update default sms subscription.
-            if (DBG) log("updateDefaultValues: Update default sms subscription");
-            smsSelected = updateDefaultValue(mPrimarySubList,
-                    mSubController.getDefaultSmsSubId(),
-                    mSubController::setDefaultSmsSubId,
-                    mIsAskEverytimeSupportedForSms);
-        }
+        // 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);
@@ -771,12 +684,7 @@
             // any previous primary subscription becomes inactive, we consider it
             for (int subId : prevPrimarySubList) {
                 if (mPrimarySubList.contains(subId)) continue;
-                SubscriptionInfo subInfo = null;
-                if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-                    subInfo = mSubscriptionManagerService.getSubscriptionInfo(subId);
-                } else {
-                    subInfo = mSubController.getSubscriptionInfo(subId);
-                }
+                SubscriptionInfo subInfo = mSubscriptionManagerService.getSubscriptionInfo(subId);
 
                 if (subInfo == null || !subInfo.isActive()) {
                     for (int currentSubId : mPrimarySubList) {
@@ -890,16 +798,10 @@
             if (phone != null && phone.isCdmaSubscriptionAppPresent()) {
                 cdmaPhoneCount++;
                 String simName = null;
-                if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-                    SubscriptionInfoInternal subInfo = mSubscriptionManagerService
-                            .getSubscriptionInfoInternal(subId);
-                    if (subInfo != null) {
-                        simName = subInfo.getDisplayName();
-                    }
-                } else {
-                    simName = mSubController.getActiveSubscriptionInfo(
-                            subId, mContext.getOpPackageName(), mContext.getAttributionTag())
-                            .getDisplayName().toString();
+                SubscriptionInfoInternal subInfo = mSubscriptionManagerService
+                        .getSubscriptionInfoInternal(subId);
+                if (subInfo != null) {
+                    simName = subInfo.getDisplayName();
                 }
                 if (TextUtils.isEmpty(simName)) {
                     // Fall back to carrier name.
@@ -925,22 +827,12 @@
     protected void disableDataForNonDefaultNonOpportunisticSubscriptions() {
         if (!isReadyToReevaluate()) return;
 
-        int defaultDataSub;
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            defaultDataSub = mSubscriptionManagerService.getDefaultDataSubId();
-        } else {
-            defaultDataSub = mSubController.getDefaultDataSubId();
-        }
+        int defaultDataSub = mSubscriptionManagerService.getDefaultDataSubId();
 
         for (Phone phone : PhoneFactory.getPhones()) {
-            boolean isOpportunistic;
-            if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-                SubscriptionInfoInternal subInfo = mSubscriptionManagerService
-                        .getSubscriptionInfoInternal(phone.getSubId());
-                isOpportunistic = subInfo != null && subInfo.isOpportunistic();
-            } else {
-                isOpportunistic = mSubController.isOpportunistic(phone.getSubId());
-            }
+            SubscriptionInfoInternal subInfo = mSubscriptionManagerService
+                    .getSubscriptionInfoInternal(phone.getSubId());
+            boolean isOpportunistic = subInfo != null && subInfo.isOpportunistic();
             if (phone.getSubId() != defaultDataSub
                     && SubscriptionManager.isValidSubscriptionId(phone.getSubId())
                     && !isOpportunistic
@@ -959,19 +851,13 @@
                 || !SubscriptionManager.isUsableSubscriptionId(subId2)) return false;
         if (subId1 == subId2) return true;
 
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            SubscriptionInfoInternal subInfo1 =
-                    mSubscriptionManagerService.getSubscriptionInfoInternal(subId1);
-            SubscriptionInfoInternal subInfo2 =
-                    mSubscriptionManagerService.getSubscriptionInfoInternal(subId2);
-            return subInfo1 != null && subInfo2 != null
-                    && !TextUtils.isEmpty(subInfo1.getGroupUuid())
-                    && subInfo1.getGroupUuid().equals(subInfo2.getGroupUuid());
-        } else {
-            ParcelUuid groupUuid1 = mSubController.getGroupUuid(subId1);
-            ParcelUuid groupUuid2 = mSubController.getGroupUuid(subId2);
-            return groupUuid1 != null && groupUuid1.equals(groupUuid2);
-        }
+        SubscriptionInfoInternal subInfo1 =
+                mSubscriptionManagerService.getSubscriptionInfoInternal(subId1);
+        SubscriptionInfoInternal subInfo2 =
+                mSubscriptionManagerService.getSubscriptionInfoInternal(subId2);
+        return subInfo1 != null && subInfo2 != null
+                && !TextUtils.isEmpty(subInfo1.getGroupUuid())
+                && subInfo1.getGroupUuid().equals(subInfo2.getGroupUuid());
     }
 
     /**
@@ -981,17 +867,11 @@
     protected void setUserDataEnabledForGroup(int subId, boolean enable) {
         log("setUserDataEnabledForGroup subId " + subId + " enable " + enable);
         List<SubscriptionInfo> infoList = null;
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            SubscriptionInfoInternal subInfo = mSubscriptionManagerService
-                    .getSubscriptionInfoInternal(subId);
-            if (subInfo != null && !subInfo.getGroupUuid().isEmpty()) {
-                infoList = mSubscriptionManagerService.getSubscriptionsInGroup(
-                        ParcelUuid.fromString(subInfo.getGroupUuid()), mContext.getOpPackageName(),
-                        mContext.getAttributionTag());
-            }
-        } else {
-            infoList = mSubController.getSubscriptionsInGroup(
-                    mSubController.getGroupUuid(subId), mContext.getOpPackageName(),
+        SubscriptionInfoInternal subInfo = mSubscriptionManagerService
+                .getSubscriptionInfoInternal(subId);
+        if (subInfo != null && !subInfo.getGroupUuid().isEmpty()) {
+            infoList = mSubscriptionManagerService.getSubscriptionsInGroup(
+                    ParcelUuid.fromString(subInfo.getGroupUuid()), mContext.getOpPackageName(),
                     mContext.getAttributionTag());
         }
 
@@ -1002,13 +882,8 @@
             // TODO: simplify when setUserDataEnabled becomes singleton
             if (info.isActive()) {
                 // For active subscription, call setUserDataEnabled through DataSettingsManager.
-                Phone phone;
-                if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-                    phone = PhoneFactory.getPhone(mSubscriptionManagerService
-                            .getPhoneId(currentSubId));
-                } else {
-                    phone = PhoneFactory.getPhone(mSubController.getPhoneId(currentSubId));
-                }
+                Phone phone = PhoneFactory.getPhone(mSubscriptionManagerService
+                        .getPhoneId(currentSubId));
                 // If enable is true and it's not opportunistic subscription, we don't enable it,
                 // as there can't be two
                 if (phone != null) {
@@ -1029,19 +904,12 @@
      * are synced.
      */
     private void setRoamingDataEnabledForGroup(int subId, boolean enable) {
-        List<SubscriptionInfo> infoList;
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            SubscriptionInfoInternal subInfo = mSubscriptionManagerService
-                    .getSubscriptionInfoInternal(subId);
-            if (subInfo == null || subInfo.getGroupUuid().isEmpty()) return;
-            infoList = SubscriptionManagerService.getInstance().getSubscriptionsInGroup(
-                    ParcelUuid.fromString(subInfo.getGroupUuid()), mContext.getOpPackageName(),
-                    mContext.getAttributionTag());
-        } else {
-            infoList = SubscriptionController.getInstance().getSubscriptionsInGroup(
-                    mSubController.getGroupUuid(subId), mContext.getOpPackageName(),
-                    mContext.getAttributionTag());
-        }
+        SubscriptionInfoInternal subInfo = mSubscriptionManagerService
+                .getSubscriptionInfoInternal(subId);
+        if (subInfo == null || subInfo.getGroupUuid().isEmpty()) return;
+        List<SubscriptionInfo> infoList = SubscriptionManagerService.getInstance()
+                .getSubscriptionsInGroup(ParcelUuid.fromString(subInfo.getGroupUuid()),
+                        mContext.getOpPackageName(), mContext.getAttributionTag());
         if (infoList == null) return;
 
         for (SubscriptionInfo info : infoList) {
@@ -1094,19 +962,10 @@
     // subscription gets deactivated or removed, we need to automatically disable the grouped
     // opportunistic subscription, which will be marked isGroupDisabled as true by SubController.
     private void deactivateGroupedOpportunisticSubscriptionIfNeeded() {
-        if (!SubscriptionInfoUpdater.isSubInfoInitialized()) return;
-
-        List<SubscriptionInfo> opptSubList;
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            opptSubList = mSubscriptionManagerService.getAllSubInfoList(
-                    mContext.getOpPackageName(), mContext.getAttributionTag()).stream()
-                    .filter(SubscriptionInfo::isOpportunistic)
-                    .collect(Collectors.toList());
-
-        } else {
-            opptSubList = mSubController.getOpportunisticSubscriptions(
-                    mContext.getOpPackageName(), mContext.getAttributionTag());
-        }
+        List<SubscriptionInfo> opptSubList = mSubscriptionManagerService.getAllSubInfoList(
+                mContext.getOpPackageName(), mContext.getAttributionTag()).stream()
+                .filter(SubscriptionInfo::isOpportunistic)
+                .collect(Collectors.toList());
 
         if (ArrayUtils.isEmpty(opptSubList)) return;
 
@@ -1142,101 +1001,54 @@
     // would be selected as preferred voice/data/sms SIM.
     private void updateUserPreferences(List<Integer> primarySubList, boolean dataSelected,
             boolean voiceSelected, boolean smsSelected) {
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-
-            // In Single SIM case or if there are no activated subs available, no need to update.
-            // EXIT.
-            if ((primarySubList.isEmpty()) || (mSubscriptionManagerService
-                    .getActiveSubInfoCountMax() == 1)) {
-                return;
-            }
-
-            if (!isRadioAvailableOnAllSubs()) {
-                log("Radio is in Invalid state, Ignore Updating User Preference!!!");
-                return;
-            }
-            final int defaultDataSubId = mSubscriptionManagerService.getDefaultDataSubId();
-
-            if (DBG) {
-                log("updateUserPreferences:  dds = " + defaultDataSubId + " voice = "
-                        + mSubscriptionManagerService.getDefaultVoiceSubId()
-                        + " sms = " + mSubscriptionManagerService.getDefaultSmsSubId());
-            }
-
-            int autoDefaultSubId = primarySubList.get(0);
-
-            if ((primarySubList.size() == 1) && !smsSelected) {
-                mSubscriptionManagerService.setDefaultSmsSubId(autoDefaultSubId);
-            }
-
-            if ((primarySubList.size() == 1) && !voiceSelected) {
-                mSubscriptionManagerService.setDefaultVoiceSubId(autoDefaultSubId);
-            }
-
-            int userPrefDataSubId = getUserPrefDataSubIdFromDB();
-
-            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 (DBG) {
-                log("updateUserPreferences: after dds = "
-                        + mSubscriptionManagerService.getDefaultDataSubId() + " voice = "
-                        + mSubscriptionManagerService.getDefaultVoiceSubId() + " sms = "
-                        + mSubscriptionManagerService.getDefaultSmsSubId());
-            }
+        // In Single SIM case or if there are no activated subs available, no need to update.
+        // EXIT.
+        if ((primarySubList.isEmpty()) || (mSubscriptionManagerService
+                .getActiveSubInfoCountMax() == 1)) {
             return;
         }
-        // In Single SIM case or if there are no activated subs available, no need to update. EXIT.
-        if ((primarySubList.isEmpty()) || (mSubController.getActiveSubInfoCountMax() == 1)) return;
 
         if (!isRadioAvailableOnAllSubs()) {
             log("Radio is in Invalid state, Ignore Updating User Preference!!!");
             return;
         }
-        final int defaultDataSubId = mSubController.getDefaultDataSubId();
+        final int defaultDataSubId = mSubscriptionManagerService.getDefaultDataSubId();
 
-        if (DBG) log("updateUserPreferences:  dds = " + defaultDataSubId + " voice = "
-                + mSubController.getDefaultVoiceSubId() +
-                " sms = " + mSubController.getDefaultSmsSubId());
+        if (DBG) {
+            log("updateUserPreferences:  dds = " + defaultDataSubId + " voice = "
+                    + mSubscriptionManagerService.getDefaultVoiceSubId()
+                    + " sms = " + mSubscriptionManagerService.getDefaultSmsSubId());
+        }
 
         int autoDefaultSubId = primarySubList.get(0);
 
         if ((primarySubList.size() == 1) && !smsSelected) {
-            mSubController.setDefaultSmsSubId(autoDefaultSubId);
+            mSubscriptionManagerService.setDefaultSmsSubId(autoDefaultSubId);
         }
 
         if ((primarySubList.size() == 1) && !voiceSelected) {
-            mSubController.setDefaultVoiceSubId(autoDefaultSubId);
+            mSubscriptionManagerService.setDefaultVoiceSubId(autoDefaultSubId);
         }
 
         int userPrefDataSubId = getUserPrefDataSubIdFromDB();
 
-        if (DBG) log("User pref subId = " + userPrefDataSubId + " current dds " + defaultDataSubId
-                 + " next active subId " + autoDefaultSubId);
+        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)) {
-            mSubController.setDefaultDataSubId(userPrefDataSubId);
+        if (primarySubList.contains(userPrefDataSubId)
+                && SubscriptionManager.isValidSubscriptionId(userPrefDataSubId)
+                && (defaultDataSubId != userPrefDataSubId)) {
+            mSubscriptionManagerService.setDefaultDataSubId(userPrefDataSubId);
         } else if (!dataSelected) {
-            mSubController.setDefaultDataSubId(autoDefaultSubId);
+            mSubscriptionManagerService.setDefaultDataSubId(autoDefaultSubId);
         }
 
-
         if (DBG) {
-            log("updateUserPreferences: after dds = " + mSubController.getDefaultDataSubId()
-                    + " voice = " + mSubController.getDefaultVoiceSubId() + " sms = "
-                    + mSubController.getDefaultSmsSubId());
+            log("updateUserPreferences: after dds = "
+                    + mSubscriptionManagerService.getDefaultDataSubId() + " voice = "
+                    + mSubscriptionManagerService.getDefaultVoiceSubId() + " sms = "
+                    + mSubscriptionManagerService.getDefaultSmsSubId());
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/NetworkIndication.java b/src/java/com/android/internal/telephony/NetworkIndication.java
index c9ebfd5..7f9ff79 100644
--- a/src/java/com/android/internal/telephony/NetworkIndication.java
+++ b/src/java/com/android/internal/telephony/NetworkIndication.java
@@ -16,14 +16,17 @@
 
 package com.android.internal.telephony;
 
+import static android.telephony.TelephonyManager.HAL_SERVICE_NETWORK;
 import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID;
 
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_PRL_CHANGED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CELL_INFO_LIST;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_EMERGENCY_NETWORK_SCAN_RESULT;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_LCEDATA_RECV;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_NETWORK_SCAN_RESULT;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_NITZ_TIME_RECEIVED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_PHYSICAL_CHANNEL_CONFIG;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_REGISTRATION_FAILED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_IMS_NETWORK_STATE_CHANGED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_NETWORK_STATE_CHANGED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESTRICTED_STATE_CHANGED;
@@ -39,6 +42,7 @@
 import android.telephony.BarringInfo;
 import android.telephony.CellIdentity;
 import android.telephony.CellInfo;
+import android.telephony.EmergencyRegResult;
 import android.telephony.LinkCapacityEstimate;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhysicalChannelConfig;
@@ -72,7 +76,7 @@
     public void barringInfoChanged(int indicationType,
             android.hardware.radio.network.CellIdentity cellIdentity,
             android.hardware.radio.network.BarringInfo[] barringInfos) {
-        mRil.processIndication(RIL.NETWORK_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_NETWORK, indicationType);
 
         if (cellIdentity == null || barringInfos == null) {
             reportAnomaly(UUID.fromString("645b16bb-c930-4c1c-9c5d-568696542e05"),
@@ -84,7 +88,7 @@
         BarringInfo cbi = new BarringInfo(RILUtils.convertHalCellIdentity(cellIdentity),
                 RILUtils.convertHalBarringInfoList(barringInfos));
 
-        mRil.mBarringInfoChangedRegistrants.notifyRegistrants(new AsyncResult(null, cbi, null));
+        mRil.notifyBarringInfoChanged(cbi);
     }
 
     /**
@@ -93,7 +97,7 @@
      * @param version PRL version after PRL changes
      */
     public void cdmaPrlChanged(int indicationType, int version) {
-        mRil.processIndication(RIL.NETWORK_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_NETWORK, indicationType);
 
         int[] response = new int[]{version};
 
@@ -109,7 +113,7 @@
      */
     public void cellInfoList(int indicationType,
             android.hardware.radio.network.CellInfo[] records) {
-        mRil.processIndication(RIL.NETWORK_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_NETWORK, indicationType);
         ArrayList<CellInfo> response = RILUtils.convertHalCellInfoList(records);
         if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_CELL_INFO_LIST, response);
         mRil.mRilCellInfoListRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
@@ -122,7 +126,7 @@
      */
     public void currentLinkCapacityEstimate(int indicationType,
             android.hardware.radio.network.LinkCapacityEstimate lce) {
-        mRil.processIndication(RIL.NETWORK_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_NETWORK, indicationType);
 
         List<LinkCapacityEstimate> response = RILUtils.convertHalLceData(lce);
 
@@ -140,27 +144,34 @@
      */
     public void currentPhysicalChannelConfigs(int indicationType,
             android.hardware.radio.network.PhysicalChannelConfig[] configs) {
-        mRil.processIndication(RIL.NETWORK_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_NETWORK, indicationType);
         List<PhysicalChannelConfig> response = new ArrayList<>(configs.length);
         try {
             for (android.hardware.radio.network.PhysicalChannelConfig config : configs) {
                 PhysicalChannelConfig.Builder builder = new PhysicalChannelConfig.Builder();
+                int band = PhysicalChannelConfig.BAND_UNKNOWN;
                 switch (config.band.getTag()) {
                     case android.hardware.radio.network.PhysicalChannelConfigBand.geranBand:
-                        builder.setBand(config.band.getGeranBand());
+                        band = config.band.getGeranBand();
                         break;
                     case android.hardware.radio.network.PhysicalChannelConfigBand.utranBand:
-                        builder.setBand(config.band.getUtranBand());
+                        band = config.band.getUtranBand();
                         break;
                     case android.hardware.radio.network.PhysicalChannelConfigBand.eutranBand:
-                        builder.setBand(config.band.getEutranBand());
+                        band = config.band.getEutranBand();
                         break;
                     case android.hardware.radio.network.PhysicalChannelConfigBand.ngranBand:
-                        builder.setBand(config.band.getNgranBand());
+                        band = config.band.getNgranBand();
                         break;
                     default:
                         mRil.riljLoge("Unsupported band type " + config.band.getTag());
                 }
+                if (band == PhysicalChannelConfig.BAND_UNKNOWN) {
+                    mRil.riljLoge("Unsupported unknown band.");
+                    return;
+                } else {
+                    builder.setBand(band);
+                }
                 response.add(builder.setCellConnectionStatus(
                         RILUtils.convertHalCellConnectionStatus(config.status))
                         .setDownlinkChannelNumber(config.downlinkChannelNumber)
@@ -191,7 +202,7 @@
      */
     public void currentSignalStrength(int indicationType,
             android.hardware.radio.network.SignalStrength signalStrength) {
-        mRil.processIndication(RIL.NETWORK_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_NETWORK, indicationType);
 
         SignalStrength ssInitial = RILUtils.convertHalSignalStrength(signalStrength);
 
@@ -209,7 +220,7 @@
      * @param indicationType Type of radio indication
      */
     public void imsNetworkStateChanged(int indicationType) {
-        mRil.processIndication(RIL.NETWORK_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_NETWORK, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_IMS_NETWORK_STATE_CHANGED);
 
@@ -223,7 +234,7 @@
      */
     public void networkScanResult(int indicationType,
             android.hardware.radio.network.NetworkScanResult result) {
-        mRil.processIndication(RIL.NETWORK_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_NETWORK, indicationType);
 
         ArrayList<CellInfo> cellInfos = RILUtils.convertHalCellInfoList(result.networkInfos);
         NetworkScanResult nsr = new NetworkScanResult(result.status, result.error, cellInfos);
@@ -236,7 +247,7 @@
      * @param indicationType Type of radio indication
      */
     public void networkStateChanged(int indicationType) {
-        mRil.processIndication(RIL.NETWORK_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_NETWORK, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_NETWORK_STATE_CHANGED);
 
@@ -256,7 +267,7 @@
      */
     public void nitzTimeReceived(int indicationType, String nitzTime,
         @ElapsedRealtimeLong long receivedTimeMs, long ageMs) {
-        mRil.processIndication(RIL.NETWORK_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_NETWORK, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_NITZ_TIME_RECEIVED, nitzTime);
 
@@ -303,23 +314,32 @@
     public void registrationFailed(int indicationType,
             android.hardware.radio.network.CellIdentity cellIdentity, String chosenPlmn,
             @NetworkRegistrationInfo.Domain int domain, int causeCode, int additionalCauseCode) {
-        mRil.processIndication(RIL.NETWORK_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_NETWORK, indicationType);
         CellIdentity ci = RILUtils.convertHalCellIdentity(cellIdentity);
         if (ci == null || TextUtils.isEmpty(chosenPlmn)
                 || (domain & NetworkRegistrationInfo.DOMAIN_CS_PS) == 0
                 || (domain & ~NetworkRegistrationInfo.DOMAIN_CS_PS) != 0
                 || causeCode < 0 || additionalCauseCode < 0
                 || (causeCode == Integer.MAX_VALUE && additionalCauseCode == Integer.MAX_VALUE)) {
-            reportAnomaly(UUID.fromString("f16e5703-6105-4341-9eb3-e68189156eb4"),
+            reportAnomaly(UUID.fromString("f16e5703-6105-4341-9eb3-e68189156eb5"),
                     "Invalid registrationFailed indication");
 
-            mRil.riljLoge("Invalid registrationFailed indication");
+            mRil.riljLoge("Invalid registrationFailed indication (ci is null)=" + (ci == null)
+                    + " (chosenPlmn is empty)=" + TextUtils.isEmpty(chosenPlmn)
+                    + " (is CS/PS)=" + ((domain & NetworkRegistrationInfo.DOMAIN_CS_PS) == 0)
+                    + " (only CS/PS)=" + ((domain & ~NetworkRegistrationInfo.DOMAIN_CS_PS) != 0)
+                    + " (causeCode)=" + causeCode
+                    + " (additionalCauseCode)=" + additionalCauseCode);
             return;
         }
 
+        RegistrationFailedEvent registrationFailedEvent = new RegistrationFailedEvent(
+                ci, chosenPlmn, domain, causeCode, additionalCauseCode);
+        if (mRil.isLogOrTrace()) {
+            mRil.unsljLogMore(RIL_UNSOL_REGISTRATION_FAILED, registrationFailedEvent.toString());
+        }
         mRil.mRegistrationFailedRegistrant.notifyRegistrant(
-                new AsyncResult(null, new RegistrationFailedEvent(
-                        ci, chosenPlmn, domain, causeCode, additionalCauseCode), null));
+                new AsyncResult(null, registrationFailedEvent, null));
     }
 
     /**
@@ -328,7 +348,7 @@
      * @param state Bitmask of restricted state as defined by PhoneRestrictedState
      */
     public void restrictedStateChanged(int indicationType, int state) {
-        mRil.processIndication(RIL.NETWORK_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_NETWORK, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLogvRet(RIL_UNSOL_RESTRICTED_STATE_CHANGED, state);
 
@@ -344,7 +364,7 @@
      */
     public void suppSvcNotify(int indicationType,
             android.hardware.radio.network.SuppSvcNotification suppSvcNotification) {
-        mRil.processIndication(RIL.NETWORK_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_NETWORK, indicationType);
 
         SuppServiceNotification notification = new SuppServiceNotification();
         notification.notificationType = suppSvcNotification.isMT ? 1 : 0;
@@ -368,7 +388,7 @@
      * @param rat Current new voice rat
      */
     public void voiceRadioTechChanged(int indicationType, int rat) {
-        mRil.processIndication(RIL.NETWORK_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_NETWORK, indicationType);
 
         int[] response = new int[] {rat};
 
@@ -380,6 +400,25 @@
                 new AsyncResult(null, response, null));
     }
 
+    /**
+     * Emergency Scan Results.
+     * @param indicationType Type of radio indication
+     * @param result the result of the Emergency Network Scan
+     */
+    public void emergencyNetworkScanResult(int indicationType,
+            android.hardware.radio.network.EmergencyRegResult result) {
+        mRil.processIndication(HAL_SERVICE_NETWORK, indicationType);
+
+        EmergencyRegResult response = RILUtils.convertHalEmergencyRegResult(result);
+
+        if (mRil.isLogOrTrace()) {
+            mRil.unsljLogRet(RIL_UNSOL_EMERGENCY_NETWORK_SCAN_RESULT, response);
+        }
+
+        mRil.mEmergencyNetworkScanRegistrants.notifyRegistrants(
+                new AsyncResult(null, response, null));
+    }
+
     @Override
     public String getInterfaceHash() {
         return IRadioNetworkIndication.HASH;
diff --git a/src/java/com/android/internal/telephony/NetworkResponse.java b/src/java/com/android/internal/telephony/NetworkResponse.java
index d9f70fd..b1eb926 100644
--- a/src/java/com/android/internal/telephony/NetworkResponse.java
+++ b/src/java/com/android/internal/telephony/NetworkResponse.java
@@ -16,12 +16,15 @@
 
 package com.android.internal.telephony;
 
+import static android.telephony.TelephonyManager.HAL_SERVICE_NETWORK;
+
 import android.hardware.radio.RadioError;
 import android.hardware.radio.RadioResponseInfo;
 import android.hardware.radio.network.IRadioNetworkResponse;
 import android.os.AsyncResult;
 import android.telephony.BarringInfo;
 import android.telephony.CellInfo;
+import android.telephony.EmergencyRegResult;
 import android.telephony.LinkCapacityEstimate;
 import android.telephony.RadioAccessSpecifier;
 import android.telephony.SignalStrength;
@@ -57,7 +60,7 @@
             int halRadioAccessFamilyBitmap) {
         int networkTypeBitmask = RILUtils.convertHalNetworkTypeBitMask(halRadioAccessFamilyBitmap);
         mRil.mAllowedNetworkTypesBitmask = networkTypeBitmask;
-        RadioResponse.responseInts(RIL.NETWORK_SERVICE, mRil, responseInfo, networkTypeBitmask);
+        RadioResponse.responseInts(HAL_SERVICE_NETWORK, mRil, responseInfo, networkTypeBitmask);
     }
 
     /**
@@ -65,7 +68,7 @@
      * @param bandModes List of RadioBandMode listing supported modes
      */
     public void getAvailableBandModesResponse(RadioResponseInfo responseInfo, int[] bandModes) {
-        RadioResponse.responseIntArrayList(RIL.NETWORK_SERVICE, mRil, responseInfo,
+        RadioResponse.responseIntArrayList(HAL_SERVICE_NETWORK, mRil, responseInfo,
                 RILUtils.primitiveArrayToArrayList(bandModes));
     }
 
@@ -75,7 +78,7 @@
      */
     public void getAvailableNetworksResponse(RadioResponseInfo responseInfo,
             android.hardware.radio.network.OperatorInfo[] networkInfos) {
-        RILRequest rr = mRil.processResponse(RIL.NETWORK_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo);
 
         if (rr != null) {
             ArrayList<OperatorInfo> ret = new ArrayList<>();
@@ -98,7 +101,7 @@
     public void getBarringInfoResponse(RadioResponseInfo responseInfo,
             android.hardware.radio.network.CellIdentity cellIdentity,
             android.hardware.radio.network.BarringInfo[] barringInfos) {
-        RILRequest rr = mRil.processResponse(RIL.NETWORK_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo);
 
         if (rr != null) {
             BarringInfo bi = new BarringInfo(RILUtils.convertHalCellIdentity(cellIdentity),
@@ -118,7 +121,7 @@
      * @param type CdmaRoamingType defined in types.hal
      */
     public void getCdmaRoamingPreferenceResponse(RadioResponseInfo responseInfo, int type) {
-        RadioResponse.responseInts(RIL.NETWORK_SERVICE, mRil, responseInfo, type);
+        RadioResponse.responseInts(HAL_SERVICE_NETWORK, mRil, responseInfo, type);
     }
 
     /**
@@ -127,7 +130,7 @@
      */
     public void getCellInfoListResponse(RadioResponseInfo responseInfo,
             android.hardware.radio.network.CellInfo[] cellInfo) {
-        RILRequest rr = mRil.processResponse(RIL.NETWORK_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo);
 
         if (rr != null) {
             ArrayList<CellInfo> ret = RILUtils.convertHalCellInfoList(cellInfo);
@@ -144,7 +147,7 @@
      */
     public void getDataRegistrationStateResponse(RadioResponseInfo responseInfo,
             android.hardware.radio.network.RegStateResult dataRegResponse) {
-        RILRequest rr = mRil.processResponse(RIL.NETWORK_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo);
 
         if (rr != null) {
             if (responseInfo.error == RadioError.NONE) {
@@ -161,7 +164,7 @@
      */
     public void getImsRegistrationStateResponse(RadioResponseInfo responseInfo,
             boolean isRegistered, int ratFamily) {
-        RadioResponse.responseInts(RIL.NETWORK_SERVICE, mRil, responseInfo, isRegistered ? 1 : 0,
+        RadioResponse.responseInts(HAL_SERVICE_NETWORK, mRil, responseInfo, isRegistered ? 1 : 0,
                 ratFamily == android.hardware.radio.RadioTechnologyFamily.THREE_GPP
                         ? PhoneConstants.PHONE_TYPE_GSM : PhoneConstants.PHONE_TYPE_CDMA);
     }
@@ -171,7 +174,7 @@
      * @param selection false for automatic selection, true for manual selection
      */
     public void getNetworkSelectionModeResponse(RadioResponseInfo responseInfo, boolean selection) {
-        RadioResponse.responseInts(RIL.NETWORK_SERVICE, mRil, responseInfo, selection ? 1 : 0);
+        RadioResponse.responseInts(HAL_SERVICE_NETWORK, mRil, responseInfo, selection ? 1 : 0);
     }
 
     /**
@@ -183,7 +186,7 @@
     public void getOperatorResponse(RadioResponseInfo responseInfo, String longName,
             String shortName, String numeric) {
         RadioResponse.responseStrings(
-                RIL.NETWORK_SERVICE, mRil, responseInfo, longName, shortName, numeric);
+                HAL_SERVICE_NETWORK, mRil, responseInfo, longName, shortName, numeric);
     }
 
     /**
@@ -192,7 +195,7 @@
      */
     public void getSignalStrengthResponse(RadioResponseInfo responseInfo,
             android.hardware.radio.network.SignalStrength signalStrength) {
-        RILRequest rr = mRil.processResponse(RIL.NETWORK_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo);
 
         if (rr != null) {
             SignalStrength ret = RILUtils.convertHalSignalStrength(signalStrength);
@@ -209,7 +212,7 @@
      */
     public void getSystemSelectionChannelsResponse(RadioResponseInfo responseInfo,
             android.hardware.radio.network.RadioAccessSpecifier[] halSpecifiers) {
-        RILRequest rr = mRil.processResponse(RIL.NETWORK_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo);
 
         if (rr != null) {
             ArrayList<RadioAccessSpecifier> specifiers = new ArrayList<>();
@@ -229,7 +232,7 @@
      * @param rat Current voice RAT
      */
     public void getVoiceRadioTechnologyResponse(RadioResponseInfo responseInfo, int rat) {
-        RadioResponse.responseInts(RIL.NETWORK_SERVICE, mRil, responseInfo, rat);
+        RadioResponse.responseInts(HAL_SERVICE_NETWORK, mRil, responseInfo, rat);
     }
 
     /**
@@ -238,7 +241,7 @@
      */
     public void getVoiceRegistrationStateResponse(RadioResponseInfo responseInfo,
             android.hardware.radio.network.RegStateResult voiceRegResponse) {
-        RILRequest rr = mRil.processResponse(RIL.NETWORK_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo);
         if (rr != null) {
             if (responseInfo.error == RadioError.NONE) {
                 RadioResponse.sendMessageResponse(rr.mResult, voiceRegResponse);
@@ -254,7 +257,7 @@
      */
     public void isNrDualConnectivityEnabledResponse(RadioResponseInfo responseInfo,
             boolean isEnabled) {
-        RILRequest rr = mRil.processResponse(RIL.NETWORK_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo);
 
         if (rr != null) {
             if (responseInfo.error == RadioError.NONE) {
@@ -270,7 +273,7 @@
      */
     public void pullLceDataResponse(RadioResponseInfo responseInfo,
             android.hardware.radio.network.LceDataInfo lceInfo) {
-        RILRequest rr = mRil.processResponse(RIL.NETWORK_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo);
 
         if (rr != null) {
             List<LinkCapacityEstimate> ret = RILUtils.convertHalLceData(lceInfo);
@@ -285,105 +288,105 @@
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setAllowedNetworkTypesBitmapResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setBandModeResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setBarringPasswordResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setCdmaRoamingPreferenceResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setCellInfoListRateResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setIndicationFilterResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setLinkCapacityReportingCriteriaResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setLocationUpdatesResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setNetworkSelectionModeAutomaticResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setNetworkSelectionModeManualResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setNrDualConnectivityStateResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setSignalStrengthReportingCriteriaResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setSuppServiceNotificationsResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial number and error.
      */
     public void setSystemSelectionChannelsResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void startNetworkScanResponse(RadioResponseInfo responseInfo) {
-        RILRequest rr = mRil.processResponse(RIL.NETWORK_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo);
         if (rr != null) {
             NetworkScanResult nsr = null;
             if (responseInfo.error == RadioError.NONE) {
@@ -399,7 +402,7 @@
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void stopNetworkScanResponse(RadioResponseInfo responseInfo) {
-        RILRequest rr = mRil.processResponse(RIL.NETWORK_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo);
         if (rr != null) {
             NetworkScanResult nsr = null;
             if (responseInfo.error == RadioError.NONE) {
@@ -417,14 +420,14 @@
      */
     public void supplyNetworkDepersonalizationResponse(RadioResponseInfo responseInfo,
             int retriesRemaining) {
-        RadioResponse.responseInts(RIL.NETWORK_SERVICE, mRil, responseInfo, retriesRemaining);
+        RadioResponse.responseInts(HAL_SERVICE_NETWORK, mRil, responseInfo, retriesRemaining);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setUsageSettingResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.NETWORK_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo);
     }
 
     /**
@@ -433,7 +436,91 @@
      */
     public void getUsageSettingResponse(RadioResponseInfo responseInfo,
             /* @TelephonyManager.UsageSetting */ int usageSetting) {
-        RadioResponse.responseInts(RIL.NETWORK_SERVICE, mRil, responseInfo, usageSetting);
+        RadioResponse.responseInts(HAL_SERVICE_NETWORK, mRil, responseInfo, usageSetting);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param regState the current registration state of the modem.
+     */
+    public void setEmergencyModeResponse(RadioResponseInfo responseInfo,
+            android.hardware.radio.network.EmergencyRegResult regState) {
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo);
+
+        if (rr != null) {
+            EmergencyRegResult response = RILUtils.convertHalEmergencyRegResult(regState);
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, response);
+            }
+            mRil.processResponseDone(rr, responseInfo, response);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void triggerEmergencyNetworkScanResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void exitEmergencyModeResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void cancelEmergencyNetworkScanResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setNullCipherAndIntegrityEnabledResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error.
+     * @param isEnabled Indicates whether null cipher and integrity is enabled, indicating
+     *                  potentially unencrypted communication
+     */
+    public void isNullCipherAndIntegrityEnabledResponse(RadioResponseInfo responseInfo,
+                    boolean isEnabled) {
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo);
+
+        if (rr != null) {
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, isEnabled);
+            }
+            mRil.processResponseDone(rr, responseInfo, isEnabled);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error.
+     * @param isEnabled Indicates whether N1 mode is enabled or not.
+     */
+    public void isN1ModeEnabledResponse(RadioResponseInfo responseInfo, boolean isEnabled) {
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo);
+
+        if (rr != null) {
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, isEnabled);
+            }
+            mRil.processResponseDone(rr, responseInfo, isEnabled);
+        }
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error.
+     */
+    public void setN1ModeEnabledResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo);
     }
 
     @Override
@@ -445,4 +532,5 @@
     public int getInterfaceVersion() {
         return IRadioNetworkResponse.VERSION;
     }
+
 }
diff --git a/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java b/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java
index b15dc59..7567566 100644
--- a/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java
+++ b/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java
@@ -197,14 +197,7 @@
     public static Set<String> getAllowedMccMncsForLocationRestrictedScan(Context context) {
         final long token = Binder.clearCallingIdentity();
         try {
-            if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-                return SubscriptionManagerService.getInstance()
-                        .getAvailableSubscriptionInfoList(context.getOpPackageName(),
-                                context.getAttributionTag()).stream()
-                        .flatMap(NetworkScanRequestTracker::getAllowableMccMncsFromSubscriptionInfo)
-                        .collect(Collectors.toSet());
-            }
-            return SubscriptionController.getInstance()
+            return SubscriptionManagerService.getInstance()
                     .getAvailableSubscriptionInfoList(context.getOpPackageName(),
                             context.getAttributionTag()).stream()
                     .flatMap(NetworkScanRequestTracker::getAllowableMccMncsFromSubscriptionInfo)
@@ -481,21 +474,21 @@
                     notifyMessenger(nsri, notifyMsg,
                             rilErrorToScanError(nsr.scanError), nsr.networkInfos);
                     if (nsr.scanStatus == NetworkScanResult.SCAN_STATUS_COMPLETE) {
-                        deleteScanAndMayNotify(nsri, NetworkScan.SUCCESS, true);
                         nsri.mPhone.mCi.unregisterForNetworkScanResult(mHandler);
+                        deleteScanAndMayNotify(nsri, NetworkScan.SUCCESS, true);
                     }
                 } else {
                     if (nsr.networkInfos != null) {
                         notifyMessenger(nsri, notifyMsg,
                                 rilErrorToScanError(nsr.scanError), nsr.networkInfos);
                     }
-                    deleteScanAndMayNotify(nsri, rilErrorToScanError(nsr.scanError), true);
                     nsri.mPhone.mCi.unregisterForNetworkScanResult(mHandler);
+                    deleteScanAndMayNotify(nsri, rilErrorToScanError(nsr.scanError), true);
                 }
             } else {
                 logEmptyResultOrException(ar);
-                deleteScanAndMayNotify(nsri, NetworkScan.ERROR_RADIO_INTERFACE_ERROR, true);
                 nsri.mPhone.mCi.unregisterForNetworkScanResult(mHandler);
+                deleteScanAndMayNotify(nsri, NetworkScan.ERROR_RADIO_INTERFACE_ERROR, true);
             }
         }
 
@@ -523,6 +516,7 @@
                 Log.e(TAG, "EVENT_STOP_NETWORK_SCAN_DONE: nsri is null");
                 return;
             }
+            nsri.mPhone.mCi.unregisterForNetworkScanResult(mHandler);
             if (ar.exception == null && ar.result != null) {
                 deleteScanAndMayNotify(nsri, NetworkScan.SUCCESS, true);
             } else {
@@ -535,7 +529,6 @@
                     Log.wtf(TAG, "EVENT_STOP_NETWORK_SCAN_DONE: ar.exception can not be null!");
                 }
             }
-            nsri.mPhone.mCi.unregisterForNetworkScanResult(mHandler);
         }
 
         // Interrupts the live scan is the scanId matches the mScanId of the mLiveRequestInfo.
diff --git a/src/java/com/android/internal/telephony/NetworkTypeController.java b/src/java/com/android/internal/telephony/NetworkTypeController.java
index 4093015..beebf22 100644
--- a/src/java/com/android/internal/telephony/NetworkTypeController.java
+++ b/src/java/com/android/internal/telephony/NetworkTypeController.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -31,7 +32,6 @@
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhysicalChannelConfig;
 import android.telephony.ServiceState;
-import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager;
 import android.telephony.data.DataCallResponse;
@@ -53,6 +53,7 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -84,30 +85,35 @@
     /** Stop all timers and go to current state. */
     public static final int EVENT_UPDATE = 0;
     /** Quit after processing all existing messages. */
-    public static final int EVENT_QUIT = 1;
-    private static final int EVENT_DATA_RAT_CHANGED = 2;
-    private static final int EVENT_NR_STATE_CHANGED = 3;
-    private static final int EVENT_NR_FREQUENCY_CHANGED = 4;
-    private static final int EVENT_PHYSICAL_LINK_STATUS_CHANGED = 5;
-    private static final int EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED = 6;
-    private static final int EVENT_CARRIER_CONFIG_CHANGED = 7;
-    private static final int EVENT_PRIMARY_TIMER_EXPIRED = 8;
-    private static final int EVENT_SECONDARY_TIMER_EXPIRED = 9;
-    private static final int EVENT_RADIO_OFF_OR_UNAVAILABLE = 10;
-    private static final int EVENT_PREFERRED_NETWORK_MODE_CHANGED = 11;
-    private static final int EVENT_INITIALIZE = 12;
-    private static final int EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED = 13;
-    private static final int EVENT_BANDWIDTH_CHANGED = 15;
-    private static final int EVENT_UPDATE_NR_ADVANCED_STATE = 16;
-    private static final int EVENT_DEVICE_IDLE_MODE_CHANGED = 17;
+    private static final int EVENT_QUIT = 1;
+    /** Initialize all events. */
+    private static final int EVENT_INITIALIZE = 2;
+    /** Event for service state changed (data rat, bandwidth, NR state, NR frequency, etc). */
+    private static final int EVENT_SERVICE_STATE_CHANGED = 3;
+    /** Event for physical link status changed. */
+    private static final int EVENT_PHYSICAL_LINK_STATUS_CHANGED = 4;
+    /** Event for physical channel config indications turned on/off. */
+    private static final int EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED = 5;
+    /** Event for carrier configs changed. */
+    private static final int EVENT_CARRIER_CONFIG_CHANGED = 6;
+    /** Event for primary timer expired. If a secondary timer exists, it will begin afterwards. */
+    private static final int EVENT_PRIMARY_TIMER_EXPIRED = 7;
+    /** Event for secondary timer expired. */
+    private static final int EVENT_SECONDARY_TIMER_EXPIRED = 8;
+    /** Event for radio off or unavailable. */
+    private static final int EVENT_RADIO_OFF_OR_UNAVAILABLE = 9;
+    /** Event for preferred network mode changed. */
+    private static final int EVENT_PREFERRED_NETWORK_MODE_CHANGED = 10;
+    /** Event for physical channel configs changed. */
+    private static final int EVENT_PHYSICAL_CHANNEL_CONFIG_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;
 
     private static final String[] sEvents = new String[EVENT_DEVICE_IDLE_MODE_CHANGED + 1];
     static {
         sEvents[EVENT_UPDATE] = "EVENT_UPDATE";
         sEvents[EVENT_QUIT] = "EVENT_QUIT";
-        sEvents[EVENT_DATA_RAT_CHANGED] = "EVENT_DATA_RAT_CHANGED";
-        sEvents[EVENT_NR_STATE_CHANGED] = "EVENT_NR_STATE_CHANGED";
-        sEvents[EVENT_NR_FREQUENCY_CHANGED] = "EVENT_NR_FREQUENCY_CHANGED";
+        sEvents[EVENT_SERVICE_STATE_CHANGED] = "EVENT_SERVICE_STATE_CHANGED";
         sEvents[EVENT_PHYSICAL_LINK_STATUS_CHANGED] = "EVENT_PHYSICAL_LINK_STATUS_CHANGED";
         sEvents[EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED] =
                 "EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED";
@@ -118,25 +124,15 @@
         sEvents[EVENT_PREFERRED_NETWORK_MODE_CHANGED] = "EVENT_PREFERRED_NETWORK_MODE_CHANGED";
         sEvents[EVENT_INITIALIZE] = "EVENT_INITIALIZE";
         sEvents[EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED] = "EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED";
-        sEvents[EVENT_BANDWIDTH_CHANGED] = "EVENT_BANDWIDTH_CHANGED";
-        sEvents[EVENT_UPDATE_NR_ADVANCED_STATE] = "EVENT_UPDATE_NR_ADVANCED_STATE";
         sEvents[EVENT_DEVICE_IDLE_MODE_CHANGED] = "EVENT_DEVICE_IDLE_MODE_CHANGED";
     }
 
-    private final Phone mPhone;
-    private final DisplayInfoController mDisplayInfoController;
-    private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+    private final @NonNull Phone mPhone;
+    private final @NonNull DisplayInfoController mDisplayInfoController;
+    private final @NonNull BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             switch (intent.getAction()) {
-                case CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED:
-                    if (intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX,
-                            SubscriptionManager.INVALID_PHONE_INDEX) == mPhone.getPhoneId()
-                            && !intent.getBooleanExtra(
-                                    CarrierConfigManager.EXTRA_REBROADCAST_ON_UNLOCK, false)) {
-                        sendMessage(EVENT_CARRIER_CONFIG_CHANGED);
-                    }
-                    break;
                 case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED:
                     sendMessage(EVENT_DEVICE_IDLE_MODE_CHANGED);
                     break;
@@ -144,9 +140,22 @@
         }
     };
 
-    private Map<String, OverrideTimerRule> mOverrideTimerRules = new HashMap<>();
-    private String mLteEnhancedPattern = "";
-    private int mOverrideNetworkType;
+    private final @NonNull CarrierConfigManager.CarrierConfigChangeListener
+            mCarrierConfigChangeListener =
+            new CarrierConfigManager.CarrierConfigChangeListener() {
+                @Override
+                public void onCarrierConfigChanged(int slotIndex, int subId, int carrierId,
+                        int specificCarrierId) {
+                    // CarrierConfigChangeListener wouldn't send notification on device unlock
+                    if (slotIndex == mPhone.getPhoneId()) {
+                        sendMessage(EVENT_CARRIER_CONFIG_CHANGED);
+                    }
+                }
+            };
+
+    private @NonNull Map<String, OverrideTimerRule> mOverrideTimerRules = new HashMap<>();
+    private @NonNull String mLteEnhancedPattern = "";
+    private @Annotation.OverrideNetworkType int mOverrideNetworkType;
     private boolean mIsPhysicalChannelConfigOn;
     private boolean mIsPrimaryTimerActive;
     private boolean mIsSecondaryTimerActive;
@@ -154,10 +163,10 @@
     private int mLtePlusThresholdBandwidth;
     private int mNrAdvancedThresholdBandwidth;
     private boolean mIncludeLteForNrAdvancedThresholdBandwidth;
-    private int[] mAdditionalNrAdvancedBandsList;
-    private String mPrimaryTimerState;
-    private String mSecondaryTimerState;
-    private String mPreviousState;
+    private @NonNull int[] mAdditionalNrAdvancedBandsList;
+    private @NonNull String mPrimaryTimerState;
+    private @NonNull String mSecondaryTimerState;
+    private @NonNull String mPreviousState;
     private @LinkStatus int mPhysicalLinkStatus;
     private boolean mIsPhysicalChannelConfig16Supported;
     private boolean mIsNrAdvancedAllowedByPco = false;
@@ -169,6 +178,10 @@
     private @Nullable DataNetworkControllerCallback mNrAdvancedCapableByPcoChangedCallback = null;
     private @Nullable DataNetworkControllerCallback mNrPhysicalLinkStatusChangedCallback = null;
 
+    // Cached copies below to prevent race conditions
+    private @NonNull ServiceState mServiceState;
+    private @Nullable List<PhysicalChannelConfig> mPhysicalChannelConfigs;
+
     /**
      * NetworkTypeController constructor.
      *
@@ -181,14 +194,22 @@
         mDisplayInfoController = displayInfoController;
         mOverrideNetworkType = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
         mIsPhysicalChannelConfigOn = true;
+        mPrimaryTimerState = "";
+        mSecondaryTimerState = "";
+        mPreviousState = "";
         DefaultState defaultState = new DefaultState();
         addState(defaultState);
         addState(mLegacyState, defaultState);
         addState(mIdleState, defaultState);
         addState(mLteConnectedState, defaultState);
         addState(mNrConnectedState, defaultState);
+        addState(mNrConnectedAdvancedState, defaultState);
         setInitialState(defaultState);
         start();
+
+        mServiceState = mPhone.getServiceStateTracker().getServiceState();
+        mPhysicalChannelConfigs = mPhone.getServiceStateTracker().getPhysicalChannelConfigList();
+
         sendMessage(EVENT_INITIALIZE);
     }
 
@@ -201,10 +222,21 @@
     }
 
     /**
-     * @return True if either the primary or secondary 5G hysteresis timer is active,
-     * and false if neither are.
+     * @return The current data network type, used to create TelephonyDisplayInfo in
+     * DisplayInfoController.
      */
-    public boolean is5GHysteresisActive() {
+    public @Annotation.NetworkType int getDataNetworkType() {
+        NetworkRegistrationInfo nri = mServiceState.getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        return nri == null ? TelephonyManager.NETWORK_TYPE_UNKNOWN
+                : nri.getAccessNetworkTechnology();
+    }
+
+    /**
+     * @return {@code true} if either the primary or secondary 5G icon timers are active,
+     * and {@code false} if neither are.
+     */
+    public boolean areAnyTimersActive() {
         return mIsPrimaryTimerActive || mIsSecondaryTimerActive;
     }
 
@@ -215,35 +247,30 @@
                 EVENT_PREFERRED_NETWORK_MODE_CHANGED, null);
         mPhone.registerForPhysicalChannelConfig(getHandler(),
                 EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED, null);
-        mPhone.getServiceStateTracker().registerForDataRegStateOrRatChanged(
-                AccessNetworkConstants.TRANSPORT_TYPE_WWAN, getHandler(),
-                EVENT_DATA_RAT_CHANGED, null);
-        mPhone.getServiceStateTracker().registerForBandwidthChanged(
-                getHandler(), EVENT_BANDWIDTH_CHANGED, null);
+        mPhone.getServiceStateTracker().registerForServiceStateChanged(getHandler(),
+                EVENT_SERVICE_STATE_CHANGED, null);
         mIsPhysicalChannelConfig16Supported = mPhone.getContext().getSystemService(
                 TelephonyManager.class).isRadioInterfaceCapabilitySupported(
                 TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
-        mPhone.getServiceStateTracker().registerForNrStateChanged(getHandler(),
-                EVENT_NR_STATE_CHANGED, null);
-        mPhone.getServiceStateTracker().registerForNrFrequencyChanged(getHandler(),
-                EVENT_NR_FREQUENCY_CHANGED, null);
         mPhone.getDeviceStateMonitor().registerForPhysicalChannelConfigNotifChanged(getHandler(),
                 EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED, null);
         IntentFilter filter = new IntentFilter();
-        filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
         filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
         mPhone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone);
+        CarrierConfigManager ccm = mPhone.getContext().getSystemService(CarrierConfigManager.class);
+        ccm.registerCarrierConfigChangeListener(Runnable::run, mCarrierConfigChangeListener);
     }
 
     private void unRegisterForAllEvents() {
         mPhone.unregisterForRadioOffOrNotAvailable(getHandler());
         mPhone.unregisterForPreferredNetworkTypeChanged(getHandler());
-        mPhone.getServiceStateTracker().unregisterForDataRegStateOrRatChanged(
-                AccessNetworkConstants.TRANSPORT_TYPE_WWAN, getHandler());
-        mPhone.getServiceStateTracker().unregisterForNrStateChanged(getHandler());
-        mPhone.getServiceStateTracker().unregisterForNrFrequencyChanged(getHandler());
+        mPhone.getServiceStateTracker().unregisterForServiceStateChanged(getHandler());
         mPhone.getDeviceStateMonitor().unregisterForPhysicalChannelConfigNotifChanged(getHandler());
         mPhone.getContext().unregisterReceiver(mIntentReceiver);
+        CarrierConfigManager ccm = mPhone.getContext().getSystemService(CarrierConfigManager.class);
+        if (mCarrierConfigChangeListener != null) {
+            ccm.unregisterCarrierConfigChangeListener(mCarrierConfigChangeListener);
+        }
     }
 
     private void parseCarrierConfigs() {
@@ -276,11 +303,10 @@
             mNrAdvancedCapableByPcoChangedCallback =
                     new DataNetworkControllerCallback(getHandler()::post) {
                         @Override
-                        public void onNrAdvancedCapableByPcoChanged(
-                                boolean nrAdvancedCapable) {
+                        public void onNrAdvancedCapableByPcoChanged(boolean nrAdvancedCapable) {
                             log("mIsNrAdvancedAllowedByPco=" + nrAdvancedCapable);
                             mIsNrAdvancedAllowedByPco = nrAdvancedCapable;
-                            sendMessage(EVENT_UPDATE_NR_ADVANCED_STATE);
+                            sendMessage(EVENT_UPDATE);
                         }
                     };
             mPhone.getDataNetworkController().registerDataNetworkControllerCallback(
@@ -324,7 +350,7 @@
         if (!TextUtils.isEmpty(icons)) {
             // Format: "STATE:ICON,STATE2:ICON2"
             for (String pair : icons.trim().split(",")) {
-                String[] kv = (pair.trim().toLowerCase()).split(":");
+                String[] kv = (pair.trim().toLowerCase(Locale.ROOT)).split(":");
                 if (kv.length != 2) {
                     if (DBG) loge("Invalid 5G icon configuration, config = " + pair);
                     continue;
@@ -351,7 +377,7 @@
         if (!TextUtils.isEmpty(timers)) {
             // Format: "FROM_STATE,TO_STATE,DURATION;FROM_STATE_2,TO_STATE_2,DURATION_2"
             for (String triple : timers.trim().split(";")) {
-                String[] kv = (triple.trim().toLowerCase()).split(",");
+                String[] kv = (triple.trim().toLowerCase(Locale.ROOT)).split(",");
                 if (kv.length != 3) {
                     if (DBG) loge("Invalid 5G icon timer configuration, config = " + triple);
                     continue;
@@ -377,7 +403,7 @@
         if (!TextUtils.isEmpty(secondaryTimers)) {
             // Format: "PRIMARY_STATE,TO_STATE,DURATION;PRIMARY_STATE_2,TO_STATE_2,DURATION_2"
             for (String triple : secondaryTimers.trim().split(";")) {
-                String[] kv = (triple.trim().toLowerCase()).split(",");
+                String[] kv = (triple.trim().toLowerCase(Locale.ROOT)).split(",");
                 if (kv.length != 3) {
                     if (DBG) {
                         loge("Invalid 5G icon secondary timer configuration, config = " + triple);
@@ -419,7 +445,7 @@
         int displayNetworkType = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
         int dataNetworkType = getDataNetworkType();
         boolean nrNsa = isLte(dataNetworkType)
-                && mPhone.getServiceState().getNrState() != NetworkRegistrationInfo.NR_STATE_NONE;
+                && mServiceState.getNrState() != NetworkRegistrationInfo.NR_STATE_NONE;
         boolean nrSa = dataNetworkType == TelephonyManager.NETWORK_TYPE_NR;
 
         // NR display is not accurate when physical channel config notifications are off
@@ -450,7 +476,7 @@
                 keys.add(STATE_CONNECTED_NR_ADVANCED);
             }
         } else {
-            switch (mPhone.getServiceState().getNrState()) {
+            switch (mServiceState.getNrState()) {
                 case NetworkRegistrationInfo.NR_STATE_CONNECTED:
                     if (isNrAdvanced()) {
                         keys.add(STATE_CONNECTED_NR_ADVANCED);
@@ -480,9 +506,9 @@
     private @Annotation.OverrideNetworkType int getLteDisplayType() {
         int value = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
         if ((getDataNetworkType() == TelephonyManager.NETWORK_TYPE_LTE_CA
-                || mPhone.getServiceState().isUsingCarrierAggregation())
-                && (IntStream.of(mPhone.getServiceState().getCellBandwidths()).sum()
-                        > mLtePlusThresholdBandwidth)) {
+                || mServiceState.isUsingCarrierAggregation())
+                && IntStream.of(mServiceState.getCellBandwidths()).sum()
+                > mLtePlusThresholdBandwidth) {
             value = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA;
         }
         if (isLteEnhancedAvailable()) {
@@ -496,8 +522,8 @@
             return false;
         }
         Pattern stringPattern = Pattern.compile(mLteEnhancedPattern);
-        for (String opName : new String[] {mPhone.getServiceState().getOperatorAlphaLongRaw(),
-                mPhone.getServiceState().getOperatorAlphaShortRaw()}) {
+        for (String opName : new String[] {mServiceState.getOperatorAlphaLongRaw(),
+                mServiceState.getOperatorAlphaShortRaw()}) {
             if (!TextUtils.isEmpty(opName)) {
                 Matcher matcher = stringPattern.matcher(opName);
                 if (matcher.find()) {
@@ -517,8 +543,6 @@
             if (DBG) log("DefaultState: process " + getEventName(msg.what));
             switch (msg.what) {
                 case EVENT_UPDATE:
-                case EVENT_PREFERRED_NETWORK_MODE_CHANGED:
-                    if (DBG) log("Reset timers since preferred network mode changed.");
                     resetAllTimers();
                     transitionToCurrentState();
                     break;
@@ -535,20 +559,10 @@
                     registerForAllEvents();
                     parseCarrierConfigs();
                     break;
-                case EVENT_DATA_RAT_CHANGED:
-                case EVENT_NR_STATE_CHANGED:
-                case EVENT_NR_FREQUENCY_CHANGED:
-                case EVENT_UPDATE_NR_ADVANCED_STATE:
-                    // ignored
-                    break;
-                case EVENT_BANDWIDTH_CHANGED:
-                    // Update in case of LTE/LTE+ switch
-                    updateOverrideNetworkType();
-                    break;
-                case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED:
-                    if (isUsingPhysicalChannelConfigForRrcDetection()) {
-                        mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
-                    }
+                case EVENT_SERVICE_STATE_CHANGED:
+                    mServiceState = mPhone.getServiceStateTracker().getServiceState();
+                    if (DBG) log("ServiceState updated: " + mServiceState);
+                    transitionToCurrentState();
                     break;
                 case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
                     AsyncResult ar = (AsyncResult) msg.obj;
@@ -590,6 +604,20 @@
                     resetAllTimers();
                     transitionTo(mLegacyState);
                     break;
+                case EVENT_PREFERRED_NETWORK_MODE_CHANGED:
+                    if (DBG) log("Reset timers since preferred network mode changed.");
+                    resetAllTimers();
+                    transitionToCurrentState();
+                    break;
+                case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED:
+                    mPhysicalChannelConfigs =
+                            mPhone.getServiceStateTracker().getPhysicalChannelConfigList();
+                    if (DBG) log("Physical channel configs updated: " + mPhysicalChannelConfigs);
+                    if (isUsingPhysicalChannelConfigForRrcDetection()) {
+                        mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
+                    }
+                    transitionToCurrentState();
+                    break;
                 case EVENT_DEVICE_IDLE_MODE_CHANGED:
                     PowerManager pm = mPhone.getContext().getSystemService(PowerManager.class);
                     mIsDeviceIdleMode = pm.isDeviceIdleMode();
@@ -636,11 +664,19 @@
         public boolean processMessage(Message msg) {
             if (DBG) log("LegacyState: process " + getEventName(msg.what));
             updateTimers();
-            int rat = getDataNetworkType();
             switch (msg.what) {
-                case EVENT_DATA_RAT_CHANGED:
+                case EVENT_SERVICE_STATE_CHANGED:
+                    mServiceState = mPhone.getServiceStateTracker().getServiceState();
+                    if (DBG) log("ServiceState updated: " + mServiceState);
+                    // fallthrough
+                case EVENT_UPDATE:
+                    int rat = getDataNetworkType();
                     if (rat == TelephonyManager.NETWORK_TYPE_NR || isLte(rat) && isNrConnected()) {
-                        transitionTo(mNrConnectedState);
+                        if (isNrAdvanced()) {
+                            transitionTo(mNrConnectedAdvancedState);
+                        } else {
+                            transitionTo(mNrConnectedState);
+                        }
                     } else if (isLte(rat) && isNrNotRestricted()) {
                         transitionWithTimerTo(isPhysicalLinkActive()
                                 ? mLteConnectedState : mIdleState);
@@ -653,21 +689,10 @@
                     }
                     mIsNrRestricted = isNrRestricted();
                     break;
-                case EVENT_NR_STATE_CHANGED:
-                    if (isNrConnected()) {
-                        transitionTo(mNrConnectedState);
-                    } else if (isLte(rat) && isNrNotRestricted()) {
-                        transitionWithTimerTo(isPhysicalLinkActive()
-                                ? mLteConnectedState : mIdleState);
-                    } else if (isLte(rat) && isNrRestricted()) {
-                        updateOverrideNetworkType();
-                    }
-                    mIsNrRestricted = isNrRestricted();
-                    break;
-                case EVENT_NR_FREQUENCY_CHANGED:
-                    // ignored
-                    break;
                 case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED:
+                    mPhysicalChannelConfigs =
+                            mPhone.getServiceStateTracker().getPhysicalChannelConfigList();
+                    if (DBG) log("Physical channel configs updated: " + mPhysicalChannelConfigs);
                     if (isUsingPhysicalChannelConfigForRrcDetection()) {
                         mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
                         if (mIsTimerResetEnabledForLegacyStateRrcIdle && !isPhysicalLinkActive()) {
@@ -675,8 +700,6 @@
                             resetAllTimers();
                         }
                     }
-                    // Update in case of LTE/LTE+ switch
-                    updateOverrideNetworkType();
                     break;
                 case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
                     AsyncResult ar = (AsyncResult) msg.obj;
@@ -723,52 +746,52 @@
             if (DBG) log("IdleState: process " + getEventName(msg.what));
             updateTimers();
             switch (msg.what) {
-                case EVENT_DATA_RAT_CHANGED:
+                case EVENT_SERVICE_STATE_CHANGED:
+                    mServiceState = mPhone.getServiceStateTracker().getServiceState();
+                    if (DBG) log("ServiceState updated: " + mServiceState);
+                    // fallthrough
+                case EVENT_UPDATE:
                     int rat = getDataNetworkType();
-                    if (rat == TelephonyManager.NETWORK_TYPE_NR) {
-                        transitionTo(mNrConnectedState);
+                    if (rat == TelephonyManager.NETWORK_TYPE_NR
+                            || (isLte(rat) && isNrConnected())) {
+                        if (isNrAdvanced()) {
+                            transitionTo(mNrConnectedAdvancedState);
+                        } else {
+                            transitionTo(mNrConnectedState);
+                        }
                     } else if (!isLte(rat) || !isNrNotRestricted()) {
                         transitionWithTimerTo(mLegacyState);
-                    }
-                    break;
-                case EVENT_NR_STATE_CHANGED:
-                    if (isNrConnected()) {
-                        transitionTo(mNrConnectedState);
-                    } else if (!isNrNotRestricted()) {
-                        transitionWithTimerTo(mLegacyState);
-                    }
-                    break;
-                case EVENT_NR_FREQUENCY_CHANGED:
-                    // ignore
-                    break;
-                case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED:
-                    if (isUsingPhysicalChannelConfigForRrcDetection()) {
-                        mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
-                        if (isNrNotRestricted()) {
-                            // NOT_RESTRICTED_RRC_IDLE -> NOT_RESTRICTED_RRC_CON
-                            if (isPhysicalLinkActive()) {
-                                transitionWithTimerTo(mLteConnectedState);
-                                break;
-                            }
+                    } else {
+                        if (isPhysicalLinkActive()) {
+                            transitionWithTimerTo(mLteConnectedState);
                         } else {
-                            log("NR state changed. Sending EVENT_NR_STATE_CHANGED");
-                            sendMessage(EVENT_NR_STATE_CHANGED);
+                            // Update in case the override network type changed
+                            updateOverrideNetworkType();
                         }
                     }
-                    // Update in case of LTE/LTE+ switch
-                    updateOverrideNetworkType();
+                    break;
+                case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED:
+                    mPhysicalChannelConfigs =
+                            mPhone.getServiceStateTracker().getPhysicalChannelConfigList();
+                    if (DBG) log("Physical channel configs updated: " + mPhysicalChannelConfigs);
+                    if (isUsingPhysicalChannelConfigForRrcDetection()) {
+                        mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
+                        if (isPhysicalLinkActive()) {
+                            transitionWithTimerTo(mLteConnectedState);
+                        } else {
+                            log("Reevaluating state due to link status changed.");
+                            sendMessage(EVENT_UPDATE);
+                        }
+                    }
                     break;
                 case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
                     AsyncResult ar = (AsyncResult) msg.obj;
                     mPhysicalLinkStatus = (int) ar.result;
-                    if (isNrNotRestricted()) {
-                        // NOT_RESTRICTED_RRC_IDLE -> NOT_RESTRICTED_RRC_CON
-                        if (isPhysicalLinkActive()) {
-                            transitionWithTimerTo(mLteConnectedState);
-                        }
+                    if (isPhysicalLinkActive()) {
+                        transitionWithTimerTo(mLteConnectedState);
                     } else {
-                        log("NR state changed. Sending EVENT_NR_STATE_CHANGED");
-                        sendMessage(EVENT_NR_STATE_CHANGED);
+                        log("Reevaluating state due to link status changed.");
+                        sendMessage(EVENT_UPDATE);
                     }
                     break;
                 default:
@@ -807,52 +830,52 @@
             if (DBG) log("LteConnectedState: process " + getEventName(msg.what));
             updateTimers();
             switch (msg.what) {
-                case EVENT_DATA_RAT_CHANGED:
+                case EVENT_SERVICE_STATE_CHANGED:
+                    mServiceState = mPhone.getServiceStateTracker().getServiceState();
+                    if (DBG) log("ServiceState updated: " + mServiceState);
+                    // fallthrough
+                case EVENT_UPDATE:
                     int rat = getDataNetworkType();
-                    if (rat == TelephonyManager.NETWORK_TYPE_NR) {
-                        transitionTo(mNrConnectedState);
+                    if (rat == TelephonyManager.NETWORK_TYPE_NR
+                            || (isLte(rat) && isNrConnected())) {
+                        if (isNrAdvanced()) {
+                            transitionTo(mNrConnectedAdvancedState);
+                        } else {
+                            transitionTo(mNrConnectedState);
+                        }
                     } else if (!isLte(rat) || !isNrNotRestricted()) {
                         transitionWithTimerTo(mLegacyState);
-                    }
-                    break;
-                case EVENT_NR_STATE_CHANGED:
-                    if (isNrConnected()) {
-                        transitionTo(mNrConnectedState);
-                    } else if (!isNrNotRestricted()) {
-                        transitionWithTimerTo(mLegacyState);
-                    }
-                    break;
-                case EVENT_NR_FREQUENCY_CHANGED:
-                    // ignore
-                    break;
-                case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED:
-                    if (isUsingPhysicalChannelConfigForRrcDetection()) {
-                        mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
-                        if (isNrNotRestricted()) {
-                            // NOT_RESTRICTED_RRC_CON -> NOT_RESTRICTED_RRC_IDLE
-                            if (!isPhysicalLinkActive()) {
-                                transitionWithTimerTo(mIdleState);
-                                break;
-                            }
+                    } else {
+                        if (!isPhysicalLinkActive()) {
+                            transitionWithTimerTo(mIdleState);
                         } else {
-                            log("NR state changed. Sending EVENT_NR_STATE_CHANGED");
-                            sendMessage(EVENT_NR_STATE_CHANGED);
+                            // Update in case the override network type changed
+                            updateOverrideNetworkType();
                         }
                     }
-                    // Update in case of LTE/LTE+ switch
-                    updateOverrideNetworkType();
+                    break;
+                case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED:
+                    mPhysicalChannelConfigs =
+                            mPhone.getServiceStateTracker().getPhysicalChannelConfigList();
+                    if (DBG) log("Physical channel configs updated: " + mPhysicalChannelConfigs);
+                    if (isUsingPhysicalChannelConfigForRrcDetection()) {
+                        mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
+                        if (!isPhysicalLinkActive()) {
+                            transitionWithTimerTo(mIdleState);
+                        } else {
+                            log("Reevaluating state due to link status changed.");
+                            sendMessage(EVENT_UPDATE);
+                        }
+                    }
                     break;
                 case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
                     AsyncResult ar = (AsyncResult) msg.obj;
                     mPhysicalLinkStatus = (int) ar.result;
-                    if (isNrNotRestricted()) {
-                        // NOT_RESTRICTED_RRC_CON -> NOT_RESTRICTED_RRC_IDLE
-                        if (!isPhysicalLinkActive()) {
-                            transitionWithTimerTo(mIdleState);
-                        }
+                    if (!isPhysicalLinkActive()) {
+                        transitionWithTimerTo(mIdleState);
                     } else {
-                        log("NR state changed. Sending EVENT_NR_STATE_CHANGED");
-                        sendMessage(EVENT_NR_STATE_CHANGED);
+                        log("Reevaluating state due to link status changed.");
+                        sendMessage(EVENT_UPDATE);
                     }
                     break;
                 default:
@@ -876,28 +899,35 @@
      * Device is connected to 5G NR as the primary or secondary cell.
      */
     private final class NrConnectedState extends State {
-        private boolean mIsNrAdvanced = false;
-
         @Override
         public void enter() {
-            if (DBG) log("Entering NrConnectedState(" + getName() + ")");
+            if (DBG) log("Entering NrConnectedState");
             updateTimers();
             updateOverrideNetworkType();
             if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) {
-                mIsNrAdvanced = isNrAdvanced();
                 mPreviousState = getName();
             }
         }
 
         @Override
         public boolean processMessage(Message msg) {
-            if (DBG) log("NrConnectedState(" + getName() + "): process " + getEventName(msg.what));
+            if (DBG) log("NrConnectedState: process " + getEventName(msg.what));
             updateTimers();
-            int rat = getDataNetworkType();
             switch (msg.what) {
-                case EVENT_DATA_RAT_CHANGED:
-                    if (rat == TelephonyManager.NETWORK_TYPE_NR || isLte(rat) && isNrConnected()) {
-                        updateOverrideNetworkType();
+                case EVENT_SERVICE_STATE_CHANGED:
+                    mServiceState = mPhone.getServiceStateTracker().getServiceState();
+                    if (DBG) log("ServiceState updated: " + mServiceState);
+                    // fallthrough
+                case EVENT_UPDATE:
+                    int rat = getDataNetworkType();
+                    if (rat == TelephonyManager.NETWORK_TYPE_NR
+                            || (isLte(rat) && isNrConnected())) {
+                        if (isNrAdvanced()) {
+                            transitionTo(mNrConnectedAdvancedState);
+                        } else {
+                            // Update in case the override network type changed
+                            updateOverrideNetworkType();
+                        }
                     } else if (isLte(rat) && isNrNotRestricted()) {
                         transitionWithTimerTo(isPhysicalLinkActive()
                                 ? mLteConnectedState : mIdleState);
@@ -905,34 +935,21 @@
                         transitionWithTimerTo(mLegacyState);
                     }
                     break;
-                case EVENT_NR_STATE_CHANGED:
-                    if (isLte(rat) && isNrNotRestricted()) {
-                        transitionWithTimerTo(isPhysicalLinkActive()
-                                ? mLteConnectedState : mIdleState);
-                    } else if (rat != TelephonyManager.NETWORK_TYPE_NR && !isNrConnected()) {
-                        transitionWithTimerTo(mLegacyState);
-                    }
-                    break;
-                case EVENT_UPDATE_NR_ADVANCED_STATE:
-                    updateNrAdvancedState();
-                    break;
-                case EVENT_NR_FREQUENCY_CHANGED:
                 case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED:
+                    mPhysicalChannelConfigs =
+                            mPhone.getServiceStateTracker().getPhysicalChannelConfigList();
+                    if (DBG) log("Physical channel configs updated: " + mPhysicalChannelConfigs);
                     if (isUsingPhysicalChannelConfigForRrcDetection()) {
                         mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
                     }
-                    updateNrAdvancedState();
+                    // Check NR advanced in case NR advanced bands were added
+                    if (isNrAdvanced()) {
+                        transitionTo(mNrConnectedAdvancedState);
+                    }
                     break;
                 case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
                     AsyncResult ar = (AsyncResult) msg.obj;
                     mPhysicalLinkStatus = (int) ar.result;
-                    if (!isNrConnected()) {
-                        log("NR state changed. Sending EVENT_NR_STATE_CHANGED");
-                        sendMessage(EVENT_NR_STATE_CHANGED);
-                    }
-                    break;
-                case EVENT_BANDWIDTH_CHANGED:
-                    updateNrAdvancedState();
                     break;
                 default:
                     return NOT_HANDLED;
@@ -945,37 +962,96 @@
 
         @Override
         public String getName() {
-            return mIsNrAdvanced ? STATE_CONNECTED_NR_ADVANCED : STATE_CONNECTED;
-        }
-
-        private void updateNrAdvancedState() {
-            if (!isNrConnected() && getDataNetworkType() != TelephonyManager.NETWORK_TYPE_NR) {
-                log("NR state changed. Sending EVENT_NR_STATE_CHANGED");
-                sendMessage(EVENT_NR_STATE_CHANGED);
-                return;
-            }
-            boolean isNrAdvanced = isNrAdvanced();
-            if (isNrAdvanced != mIsNrAdvanced) {
-                if (!isNrAdvanced) {
-                    if (DBG) log("updateNrAdvancedState: CONNECTED_NR_ADVANCED -> CONNECTED");
-                    transitionWithTimerTo(mNrConnectedState, STATE_CONNECTED);
-                } else {
-                    if (DBG) log("updateNrAdvancedState: CONNECTED -> CONNECTED_NR_ADVANCED");
-                    transitionTo(mNrConnectedState);
-                }
-            }
-            mIsNrAdvanced = isNrAdvanced();
-            log("mIsNrAdvanced=" + mIsNrAdvanced);
+            return STATE_CONNECTED;
         }
     }
 
     private final NrConnectedState mNrConnectedState = new NrConnectedState();
 
-    private void transitionWithTimerTo(IState destState) {
-        transitionWithTimerTo(destState, destState.getName());
+    /**
+     * Device is connected to 5G NR as the primary cell and the data rate is higher than
+     * the generic 5G data rate.
+     */
+    private final class NrConnectedAdvancedState extends State {
+        @Override
+        public void enter() {
+            if (DBG) log("Entering NrConnectedAdvancedState");
+            updateTimers();
+            updateOverrideNetworkType();
+            if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) {
+                mPreviousState = getName();
+            }
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (DBG) log("NrConnectedAdvancedState: process " + getEventName(msg.what));
+            updateTimers();
+            switch (msg.what) {
+                case EVENT_SERVICE_STATE_CHANGED:
+                    mServiceState = mPhone.getServiceStateTracker().getServiceState();
+                    if (DBG) log("ServiceState updated: " + mServiceState);
+                    // fallthrough
+                case EVENT_UPDATE:
+                    int rat = getDataNetworkType();
+                    if (rat == TelephonyManager.NETWORK_TYPE_NR
+                            || (isLte(rat) && isNrConnected())) {
+                        if (isNrAdvanced()) {
+                            // Update in case the override network type changed
+                            updateOverrideNetworkType();
+                        } else {
+                            if (rat == TelephonyManager.NETWORK_TYPE_NR && mOverrideNetworkType
+                                    != TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED) {
+                                // manually override network type after data rat changes since
+                                // timer will prevent it from being updated
+                                mOverrideNetworkType =
+                                        TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
+                            }
+                            transitionWithTimerTo(mNrConnectedState);
+                        }
+                    } else if (isLte(rat) && isNrNotRestricted()) {
+                        transitionWithTimerTo(isPhysicalLinkActive()
+                                ? mLteConnectedState : mIdleState);
+                    } else {
+                        transitionWithTimerTo(mLegacyState);
+                    }
+                    break;
+                case EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED:
+                    mPhysicalChannelConfigs =
+                            mPhone.getServiceStateTracker().getPhysicalChannelConfigList();
+                    if (DBG) log("Physical channel configs updated: " + mPhysicalChannelConfigs);
+                    if (isUsingPhysicalChannelConfigForRrcDetection()) {
+                        mPhysicalLinkStatus = getPhysicalLinkStatusFromPhysicalChannelConfig();
+                    }
+                    // Check NR advanced in case NR advanced bands were removed
+                    if (!isNrAdvanced()) {
+                        transitionWithTimerTo(mNrConnectedState);
+                    }
+                    break;
+                case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    mPhysicalLinkStatus = (int) ar.result;
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            if (!mIsPrimaryTimerActive && !mIsSecondaryTimerActive) {
+                mPreviousState = getName();
+            }
+            return HANDLED;
+        }
+
+        @Override
+        public String getName() {
+            return STATE_CONNECTED_NR_ADVANCED;
+        }
     }
 
-    private void transitionWithTimerTo(IState destState, String destName) {
+    private final NrConnectedAdvancedState mNrConnectedAdvancedState =
+            new NrConnectedAdvancedState();
+
+    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) {
@@ -1011,22 +1087,23 @@
     private void transitionToCurrentState() {
         int dataRat = getDataNetworkType();
         IState transitionState;
-        if (dataRat == TelephonyManager.NETWORK_TYPE_NR || isNrConnected()) {
-            transitionState = mNrConnectedState;
-            mPreviousState = isNrAdvanced() ? STATE_CONNECTED_NR_ADVANCED : STATE_CONNECTED;
+        if (dataRat == TelephonyManager.NETWORK_TYPE_NR || (isLte(dataRat) && isNrConnected())) {
+            if (isNrAdvanced()) {
+                transitionState = mNrConnectedAdvancedState;
+            } else {
+                transitionState = mNrConnectedState;
+            }
         } else if (isLte(dataRat) && isNrNotRestricted()) {
             if (isPhysicalLinkActive()) {
                 transitionState = mLteConnectedState;
-                mPreviousState = STATE_NOT_RESTRICTED_RRC_CON;
             } else {
                 transitionState = mIdleState;
-                mPreviousState = STATE_NOT_RESTRICTED_RRC_IDLE;
             }
         } else {
             transitionState = mLegacyState;
-            mPreviousState = isNrRestricted() ? STATE_RESTRICTED : STATE_LEGACY;
         }
         if (!transitionState.equals(getCurrentState())) {
+            mPreviousState = getCurrentState().getName();
             transitionTo(transitionState);
         } else {
             updateOverrideNetworkType();
@@ -1073,12 +1150,17 @@
             if (currentState.equals(STATE_CONNECTED_NR_ADVANCED)) {
                 if (DBG) log("Reset timers since state is NR_ADVANCED.");
                 resetAllTimers();
-            }
-
-            int rat = getDataNetworkType();
-            if (!isLte(rat) && rat != TelephonyManager.NETWORK_TYPE_NR) {
-                if (DBG) log("Reset timers since 2G and 3G don't need NR timers.");
+            } else if (currentState.equals(STATE_CONNECTED)
+                    && !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");
                 resetAllTimers();
+            } else {
+                int rat = getDataNetworkType();
+                if (!isLte(rat) && rat != TelephonyManager.NETWORK_TYPE_NR) {
+                    if (DBG) log("Reset timers since 2G and 3G don't need NR timers.");
+                    resetAllTimers();
+                }
             }
         }
     }
@@ -1175,17 +1257,15 @@
     }
 
     private boolean isNrConnected() {
-        return mPhone.getServiceState().getNrState() == NetworkRegistrationInfo.NR_STATE_CONNECTED;
+        return mServiceState.getNrState() == NetworkRegistrationInfo.NR_STATE_CONNECTED;
     }
 
     private boolean isNrNotRestricted() {
-        return mPhone.getServiceState().getNrState()
-                == NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED;
+        return mServiceState.getNrState() == NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED;
     }
 
     private boolean isNrRestricted() {
-        return mPhone.getServiceState().getNrState()
-                == NetworkRegistrationInfo.NR_STATE_RESTRICTED;
+        return mServiceState.getNrState() == NetworkRegistrationInfo.NR_STATE_RESTRICTED;
     }
 
     /**
@@ -1200,7 +1280,7 @@
 
         // Check if NR advanced is enabled when the device is roaming. Some carriers disable it
         // while the device is roaming.
-        if (mPhone.getServiceState().getDataRoaming() && !mEnableNrAdvancedWhileRoaming) {
+        if (mServiceState.getDataRoaming() && !mEnableNrAdvancedWhileRoaming) {
             return false;
         }
 
@@ -1214,6 +1294,7 @@
                     .mapToInt(Integer::intValue)
                     .sum();
         }
+
         // Check if meeting minimum bandwidth requirement. For most carriers, there is no minimum
         // bandwidth requirement and mNrAdvancedThresholdBandwidth is 0.
         if (mNrAdvancedThresholdBandwidth > 0 && bandwidths < mNrAdvancedThresholdBandwidth) {
@@ -1226,18 +1307,15 @@
     }
 
     private boolean isNrMmwave() {
-        return mPhone.getServiceState().getNrFrequencyRange()
-                == ServiceState.FREQUENCY_RANGE_MMWAVE;
+        return mServiceState.getNrFrequencyRange() == ServiceState.FREQUENCY_RANGE_MMWAVE;
     }
 
     private boolean isAdditionalNrAdvancedBand() {
-        List<PhysicalChannelConfig> physicalChannelConfigList =
-                mPhone.getServiceStateTracker().getPhysicalChannelConfigList();
         if (ArrayUtils.isEmpty(mAdditionalNrAdvancedBandsList)
-                || physicalChannelConfigList == null) {
+                || mPhysicalChannelConfigs == null) {
             return false;
         }
-        for (PhysicalChannelConfig item : physicalChannelConfigList) {
+        for (PhysicalChannelConfig item : mPhysicalChannelConfigs) {
             if (item.getNetworkType() == TelephonyManager.NETWORK_TYPE_NR
                     && ArrayUtils.contains(mAdditionalNrAdvancedBandsList, item.getBand())) {
                 return true;
@@ -1256,19 +1334,10 @@
     }
 
     private int getPhysicalLinkStatusFromPhysicalChannelConfig() {
-        List<PhysicalChannelConfig> physicalChannelConfigList =
-                mPhone.getServiceStateTracker().getPhysicalChannelConfigList();
-        return (physicalChannelConfigList == null || physicalChannelConfigList.isEmpty())
+        return (mPhysicalChannelConfigs == null || mPhysicalChannelConfigs.isEmpty())
                 ? DataCallResponse.LINK_STATUS_DORMANT : DataCallResponse.LINK_STATUS_ACTIVE;
     }
 
-    private int getDataNetworkType() {
-        NetworkRegistrationInfo nri =  mPhone.getServiceState().getNetworkRegistrationInfo(
-                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-        return nri == null ? TelephonyManager.NETWORK_TYPE_UNKNOWN
-                : nri.getAccessNetworkTechnology();
-    }
-
     private String getEventName(int event) {
         try {
             return sEvents[event];
diff --git a/src/java/com/android/internal/telephony/NitzSignal.java b/src/java/com/android/internal/telephony/NitzSignal.java
index 889fe95..2619f3d 100644
--- a/src/java/com/android/internal/telephony/NitzSignal.java
+++ b/src/java/com/android/internal/telephony/NitzSignal.java
@@ -19,7 +19,7 @@
 import android.annotation.DurationMillisLong;
 import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
-import android.os.TimestampedValue;
+import android.app.time.UnixEpochTime;
 
 import java.time.Duration;
 import java.util.Objects;
@@ -88,13 +88,12 @@
     }
 
     /**
-     * Creates a {@link android.os.TimestampedValue} containing the UTC time as the number of
-     * milliseconds since the start of the Unix epoch. The reference time is the time according to
-     * the elapsed realtime clock when that would have been the time, accounting for receipt time
-     * and age.
+     * Creates a {@link UnixEpochTime} containing the UTC time as the number of milliseconds since
+     * the start of the Unix epoch. The reference time is the time according to the elapsed realtime
+     * clock when that would have been the time, accounting for receipt time and age.
      */
-    public TimestampedValue<Long> createTimeSignal() {
-        return new TimestampedValue<>(
+    public UnixEpochTime createTimeSignal() {
+        return new UnixEpochTime(
                 getAgeAdjustedElapsedRealtimeMillis(),
                 getNitzData().getCurrentTimeInMillis());
     }
diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java
index 5ce33c2..4e62d20 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony;
 
+import static android.telephony.TelephonyManager.HAL_SERVICE_RADIO;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.BroadcastOptions;
@@ -24,6 +26,7 @@
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.res.Configuration;
+import android.hardware.radio.modem.ImeiInfo;
 import android.net.Uri;
 import android.os.AsyncResult;
 import android.os.Build;
@@ -35,17 +38,21 @@
 import android.os.RegistrantList;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.WorkSource;
 import android.preference.PreferenceManager;
-import android.provider.DeviceConfig;
 import android.sysprop.TelephonyProperties;
 import android.telecom.VideoProfile;
 import android.telephony.AccessNetworkConstants;
+import android.telephony.Annotation.SrvccState;
 import android.telephony.CarrierConfigManager;
 import android.telephony.CarrierRestrictionRules;
+import android.telephony.CellBroadcastIdRange;
 import android.telephony.CellIdentity;
 import android.telephony.CellInfo;
 import android.telephony.ClientRequestStats;
+import android.telephony.DomainSelectionService;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.LinkCapacityEstimate;
 import android.telephony.NetworkRegistrationInfo;
@@ -60,9 +67,12 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager;
+import android.telephony.TelephonyManager.HalService;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.RegistrationManager;
+import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.telephony.satellite.SatelliteDatagram;
 import android.text.TextUtils;
 import android.util.LocalLog;
 import android.util.Log;
@@ -78,7 +88,11 @@
 import com.android.internal.telephony.data.DataNetworkController;
 import com.android.internal.telephony.data.DataSettingsManager;
 import com.android.internal.telephony.data.LinkBandwidthEstimator;
+import com.android.internal.telephony.domainselection.DomainSelectionResolver;
+import com.android.internal.telephony.emergency.EmergencyConstants;
 import com.android.internal.telephony.emergency.EmergencyNumberTracker;
+import com.android.internal.telephony.emergency.EmergencyStateTracker;
+import com.android.internal.telephony.imsphone.ImsCallInfo;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneCall;
 import com.android.internal.telephony.metrics.SmsStats;
@@ -168,7 +182,8 @@
     protected static final int EVENT_RADIO_ON                    = 5;
     protected static final int EVENT_GET_BASEBAND_VERSION_DONE   = 6;
     protected static final int EVENT_USSD                        = 7;
-    protected static final int EVENT_RADIO_OFF_OR_NOT_AVAILABLE  = 8;
+    @VisibleForTesting
+    public static final int EVENT_RADIO_OFF_OR_NOT_AVAILABLE  = 8;
     private static final int EVENT_GET_SIM_STATUS_DONE           = 11;
     protected static final int EVENT_SET_CALL_FORWARD_DONE       = 12;
     protected static final int EVENT_GET_CALL_FORWARD_DONE       = 13;
@@ -232,8 +247,12 @@
     protected static final int EVENT_SUBSCRIPTIONS_CHANGED = 62;
     protected static final int EVENT_GET_USAGE_SETTING_DONE = 63;
     protected static final int EVENT_SET_USAGE_SETTING_DONE = 64;
+    protected static final int EVENT_IMS_DEREGISTRATION_TRIGGERED = 65;
+    protected static final int EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE = 66;
+    protected static final int EVENT_GET_DEVICE_IMEI_DONE = 67;
+    protected static final int EVENT_TRIGGER_NOTIFY_ANBR = 68;
 
-    protected static final int EVENT_LAST = EVENT_SET_USAGE_SETTING_DONE;
+    protected static final int EVENT_LAST = EVENT_TRIGGER_NOTIFY_ANBR;
 
     // For shared prefs.
     private static final String GSM_ROAMING_LIST_OVERRIDE_PREFIX = "gsm_roaming_list_";
@@ -260,6 +279,10 @@
     // Integer used to let the calling application know that the we are ignoring auto mode switch.
     private static final int ALREADY_IN_AUTO_SELECTION = 1;
 
+    public static final String PREF_NULL_CIPHER_AND_INTEGRITY_ENABLED =
+            "pref_null_cipher_and_integrity_enabled";
+    private final TelephonyAdminReceiver m2gAdminUpdater;
+
     /**
      * This method is invoked when the Phone exits Emergency Callback Mode.
      */
@@ -328,7 +351,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     protected AtomicReference<UiccCardApplication> mUiccApplication =
             new AtomicReference<UiccCardApplication>();
-    TelephonyTester mTelephonyTester;
+    private TelephonyTester mTelephonyTester;
     private String mName;
     private final String mActionDetached;
     private final String mActionAttached;
@@ -357,12 +380,6 @@
     private int mUsageSettingFromModem = SubscriptionManager.USAGE_SETTING_UNKNOWN;
     private boolean mIsUsageSettingSupported = true;
 
-    /**
-     * {@code true} if the new SubscriptionManagerService is enabled, otherwise the old
-     * SubscriptionController is used.
-     */
-    private boolean mIsSubscriptionManagerServiceEnabled = false;
-
     //IMS
     /**
      * {@link CallStateException} message text used to indicate that an IMS call has failed because
@@ -391,7 +408,7 @@
     public static final String EXTRA_KEY_ALERT_SHOW = "alertShow";
     public static final String EXTRA_KEY_NOTIFICATION_MESSAGE = "notificationMessage";
 
-    private final RegistrantList mPreciseCallStateRegistrants = new RegistrantList();
+    protected final RegistrantList mPreciseCallStateRegistrants = new RegistrantList();
 
     private final RegistrantList mHandoverRegistrants = new RegistrantList();
 
@@ -466,6 +483,10 @@
 
     protected LinkBandwidthEstimator mLinkBandwidthEstimator;
 
+    public static final int IMEI_TYPE_UNKNOWN = -1;
+    public static final int IMEI_TYPE_PRIMARY = ImeiInfo.ImeiType.PRIMARY;
+    public static final int IMEI_TYPE_SECONDARY = ImeiInfo.ImeiType.SECONDARY;
+
     public IccRecords getIccRecords() {
         return mIccRecords.get();
     }
@@ -603,14 +624,8 @@
         // Initialize SMS stats
         mSmsStats = new SmsStats(this);
 
-        // This is a temp flag which will be removed before U AOSP public release.
-        mIsSubscriptionManagerServiceEnabled = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_using_subscription_manager_service)
-                || DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TELEPHONY,
-                "enable_subscription_manager_service", false);
-        if (isSubscriptionManagerServiceEnabled()) {
-            mSubscriptionManagerService = SubscriptionManagerService.getInstance();
-        }
+        mSubscriptionManagerService = SubscriptionManagerService.getInstance();
+        m2gAdminUpdater = new TelephonyAdminReceiver(context, this);
 
         if (getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
             return;
@@ -860,7 +875,11 @@
         return null;
     }
 
-    public void notifySrvccState(Call.SrvccState state) {
+    /**
+     * Notifies the change of the SRVCC state.
+     * @param state the new SRVCC state.
+     */
+    public void notifySrvccState(@SrvccState int state) {
     }
 
     public void registerForSilentRedial(Handler h, int what, Object obj) {
@@ -883,6 +902,9 @@
         Call.SrvccState srvccState = Call.SrvccState.NONE;
         if (ret != null && ret.length != 0) {
             int state = ret[0];
+            if (imsPhone != null) {
+                imsPhone.notifySrvccState(state);
+            }
             switch(state) {
                 case TelephonyManager.SRVCC_STATE_HANDOVER_STARTED:
                     srvccState = Call.SrvccState.STARTED;
@@ -895,11 +917,6 @@
                     break;
                 case TelephonyManager.SRVCC_STATE_HANDOVER_COMPLETED:
                     srvccState = Call.SrvccState.COMPLETED;
-                    if (imsPhone != null) {
-                        imsPhone.notifySrvccState(srvccState);
-                    } else {
-                        Rlog.d(LOG_TAG, "HANDOVER_COMPLETED: mImsPhone null");
-                    }
                     break;
                 case TelephonyManager.SRVCC_STATE_HANDOVER_FAILED:
                 case TelephonyManager.SRVCC_STATE_HANDOVER_CANCELED:
@@ -974,17 +991,6 @@
     }
 
     /**
-     * Subclasses of Phone probably want to replace this with a
-     * version scoped to their packages
-     */
-    protected void notifyPreciseCallStateChangedP() {
-        AsyncResult ar = new AsyncResult(null, this, null);
-        mPreciseCallStateRegistrants.notifyRegistrants(ar);
-
-        mNotifier.notifyPreciseCallState(this);
-    }
-
-    /**
      * Notifies when a Handover happens due to SRVCC or Silent Redial
      */
     public void registerForHandoverStateChanged(Handler h, int what, Object obj) {
@@ -1431,8 +1437,8 @@
     @UnsupportedAppUsage
     public void setNetworkSelectionModeAutomatic(Message response) {
         Rlog.d(LOG_TAG, "setNetworkSelectionModeAutomatic, querying current mode");
-        // we don't want to do this unecesarily - it acutally causes
-        // the radio to repeate network selection and is costly
+        // we don't want to do this unnecessarily - it actually causes
+        // the radio to repeat network selection and is costly
         // first check if we're already in automatic mode
         Message msg = obtainMessage(EVENT_CHECK_FOR_NETWORK_AUTOMATIC);
         msg.obj = response;
@@ -1465,6 +1471,7 @@
         nsm.operatorAlphaShort = "";
 
         if (doAutomatic) {
+            Rlog.d(LOG_TAG, "setNetworkSelectionModeAutomatic - set network selection auto");
             Message msg = obtainMessage(EVENT_SET_NETWORK_AUTOMATIC_COMPLETE, nsm);
             mCi.setNetworkSelectionModeAutomatic(msg);
         } else {
@@ -1921,7 +1928,7 @@
     public boolean isRadioOffForThermalMitigation() {
         ServiceStateTracker sst = getServiceStateTracker();
         return sst != null && sst.getRadioPowerOffReasons()
-                .contains(Phone.RADIO_POWER_REASON_THERMAL);
+                .contains(TelephonyManager.RADIO_POWER_REASON_THERMAL);
     }
 
     /**
@@ -2308,6 +2315,12 @@
         if (!mIsCarrierNrSupported) {
             allowedNetworkTypes &= ~TelephonyManager.NETWORK_TYPE_BITMASK_NR;
         }
+        if (m2gAdminUpdater.isCellular2gDisabled()) {
+            logd("SubId " + getSubId()
+                    + " disabling 2g in getEffectiveAllowedNetworkTypes according to admin user "
+                    + "restriction");
+            allowedNetworkTypes &= ~TelephonyManager.NETWORK_CLASS_BITMASK_2G;
+        }
         logd("SubId" + getSubId() + ",getEffectiveAllowedNetworkTypes: "
                 + TelephonyManager.convertNetworkTypeBitmaskToString(allowedNetworkTypes));
         return allowedNetworkTypes;
@@ -2384,21 +2397,10 @@
      */
     public void loadAllowedNetworksFromSubscriptionDatabase() {
         String result = null;
-        if (isSubscriptionManagerServiceEnabled()) {
-            SubscriptionInfoInternal subInfo = mSubscriptionManagerService
-                    .getSubscriptionInfoInternal(getSubId());
-            if (subInfo != null) {
-                result = subInfo.getAllowedNetworkTypesForReasons();
-            }
-        } else {
-            // Try to load ALLOWED_NETWORK_TYPES from SIMINFO.
-            if (SubscriptionController.getInstance() == null) {
-                return;
-            }
-
-            result = SubscriptionController.getInstance().getSubscriptionProperty(
-                    getSubId(),
-                    SubscriptionManager.ALLOWED_NETWORK_TYPES);
+        SubscriptionInfoInternal subInfo = mSubscriptionManagerService
+                .getSubscriptionInfoInternal(getSubId());
+        if (subInfo != null) {
+            result = subInfo.getAllowedNetworkTypesForReasons();
         }
 
         // After fw load network type from DB, do unlock if subId is valid.
@@ -2415,14 +2417,14 @@
         try {
             // Format: "REASON=VALUE,REASON2=VALUE2"
             for (String pair : result.trim().split(",")) {
-                String[] networkTypesValues = (pair.trim().toLowerCase()).split("=");
+                String[] networkTypesValues = (pair.trim().toLowerCase(Locale.ROOT)).split("=");
                 if (networkTypesValues.length != 2) {
                     Rlog.e(LOG_TAG, "Invalid ALLOWED_NETWORK_TYPES from DB, value = " + pair);
                     continue;
                 }
                 int key = convertAllowedNetworkTypeDbNameToMapIndex(networkTypesValues[0]);
                 long value = Long.parseLong(networkTypesValues[1]);
-                if (key != INVALID_ALLOWED_NETWORK_TYPES
+                if (TelephonyManager.isValidAllowedNetworkTypesReason(key)
                         && value != INVALID_ALLOWED_NETWORK_TYPES) {
                     synchronized (mAllowedNetworkTypesForReasons) {
                         mAllowedNetworkTypesForReasons.put(key, value);
@@ -2479,7 +2481,10 @@
             case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G:
                 return ALLOWED_NETWORK_TYPES_TEXT_ENABLE_2G;
             default:
-                return Integer.toString(INVALID_ALLOWED_NETWORK_TYPES);
+                throw new IllegalArgumentException(
+                        "No DB name conversion available for allowed network type reason: " + reason
+                                + ". Did you forget to add an ALLOWED_NETWORK_TYPE_TEXT entry for"
+                                + " a new reason?");
         }
     }
 
@@ -2979,6 +2984,9 @@
     // This property is used to handle phone process crashes, and is the same for CDMA and IMS
     // phones
     protected static boolean getInEcmMode() {
+        if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
+            return EmergencyStateTracker.getInstance().isInEcm();
+        }
         return TelephonyProperties.in_ecm_mode().orElse(false);
     }
 
@@ -2988,6 +2996,9 @@
      * emergency operator.
      */
     public boolean isInEcm() {
+        if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
+            return EmergencyStateTracker.getInstance().isInEcm();
+        }
         return mIsPhoneInEcmState;
     }
 
@@ -2996,6 +3007,9 @@
     }
 
     public boolean isInCdmaEcm() {
+        if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
+            return EmergencyStateTracker.getInstance().isInCdmaEcm();
+        }
         return getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA && isInEcm()
                 && (mImsPhone == null || !mImsPhone.isInImsEcm());
     }
@@ -4081,17 +4095,7 @@
      */
     @UnsupportedAppUsage
     public int getSubId() {
-        if (isSubscriptionManagerServiceEnabled()) {
-            return mSubscriptionManagerService.getSubId(mPhoneId);
-        }
-        if (SubscriptionController.getInstance() == null) {
-            // TODO b/78359408 getInstance sometimes returns null in Treehugger tests, which causes
-            // flakiness. Even though we haven't seen this crash in the wild we should keep this
-            // check in until we've figured out the root cause.
-            Rlog.e(LOG_TAG, "SubscriptionController.getInstance = null! Returning default subId");
-            return SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
-        }
-        return SubscriptionController.getInstance().getSubId(mPhoneId);
+        return mSubscriptionManagerService.getSubId(mPhoneId);
     }
 
     /**
@@ -4387,6 +4391,7 @@
         // When radio capability switch is done, query IMEI value and update it in Phone objects
         // to make it in sync with the IMEI value currently used by Logical-Modem.
         if (capabilitySwitched) {
+            mCi.getImei(obtainMessage(EVENT_GET_DEVICE_IMEI_DONE));
             mCi.getDeviceIdentity(obtainMessage(EVENT_GET_DEVICE_IDENTITY_DONE));
         }
     }
@@ -4406,14 +4411,10 @@
 
     private int getResolvedUsageSetting(int subId) {
         SubscriptionInfo subInfo = null;
-        if (isSubscriptionManagerServiceEnabled()) {
-            SubscriptionInfoInternal subInfoInternal = mSubscriptionManagerService
-                    .getSubscriptionInfoInternal(subId);
-            if (subInfoInternal != null) {
-                subInfo = subInfoInternal.toSubscriptionInfo();
-            }
-        } else {
-            subInfo = SubscriptionController.getInstance().getSubscriptionInfo(subId);
+        SubscriptionInfoInternal subInfoInternal = mSubscriptionManagerService
+                .getSubscriptionInfoInternal(subId);
+        if (subInfoInternal != null) {
+            subInfo = subInfoInternal.toSubscriptionInfo();
         }
 
         if (subInfo == null
@@ -4750,10 +4751,24 @@
      * Get the HAL version.
      *
      * @return the current HalVersion
+     *
+     * @deprecated Use {@link #getHalVersion(int service)} instead.
      */
+    @Deprecated
     public HalVersion getHalVersion() {
+        return getHalVersion(HAL_SERVICE_RADIO);
+    }
+
+    /**
+     * Get the HAL version with a specific service.
+     *
+     * @param service the service id to query
+     * @return the current HalVersion for a specific service
+     *
+     */
+    public HalVersion getHalVersion(@HalService int service) {
         if (mCi != null && mCi instanceof RIL) {
-            return ((RIL) mCi).getHalVersion();
+            return ((RIL) mCi).getHalVersion(service);
         }
         return RIL.RADIO_HAL_VERSION_UNKNOWN;
     }
@@ -4897,11 +4912,600 @@
     }
 
     /**
-     * @return {@code true} if the new {@link SubscriptionManagerService} is enabled, otherwise the
-     * old {@link SubscriptionController} is used.
+     * Returns the user's last setting for terminal-based call waiting
+     * @param forCsOnly indicates the caller expects the result for CS calls only
      */
-    public boolean isSubscriptionManagerServiceEnabled() {
-        return mIsSubscriptionManagerServiceEnabled;
+    public int getTerminalBasedCallWaitingState(boolean forCsOnly) {
+        return CallWaitingController.TERMINAL_BASED_NOT_SUPPORTED;
+    }
+
+    /**
+     * Notifies the change of the user setting of the terminal-based call waiting service
+     * to IMS service.
+     */
+    public void setTerminalBasedCallWaitingStatus(int state) {
+    }
+
+    /**
+     * Notifies that the IMS service connected supports the terminal-based call waiting service
+     */
+    public void setTerminalBasedCallWaitingSupported(boolean supported) {
+    }
+
+    /**
+     * Notifies the NAS and RRC layers of the radio the type of upcoming IMS traffic.
+     *
+     * @param token A nonce to identify the request.
+     * @param trafficType IMS traffic type like registration, voice, video, SMS, emergency, and etc.
+     * @param accessNetworkType The type of the radio access network used.
+     * @param trafficDirection Indicates whether traffic is originated by mobile originated or
+     *        mobile terminated use case eg. MO/MT call/SMS etc.
+     * @param response is callback message.
+     */
+    public void startImsTraffic(int token,
+            @MmTelFeature.ImsTrafficType int trafficType,
+            @AccessNetworkConstants.RadioAccessNetworkType int accessNetworkType,
+            @MmTelFeature.ImsTrafficDirection int trafficDirection, Message response) {
+        mCi.startImsTraffic(token, trafficType, accessNetworkType, trafficDirection, response);
+    }
+
+    /**
+     * Notifies IMS traffic has been stopped.
+     *
+     * @param token The token assigned by startImsTraffic.
+     * @param response is callback message.
+     */
+    public void stopImsTraffic(int token, Message response) {
+        mCi.stopImsTraffic(token, response);
+    }
+
+    /**
+     * Register for notifications of connection setup failure
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForConnectionSetupFailure(Handler h, int what, Object obj) {
+        mCi.registerForConnectionSetupFailure(h, what, obj);
+    }
+
+    /**
+     * Unregister for notifications of connection setup failure
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForConnectionSetupFailure(Handler h) {
+        mCi.unregisterForConnectionSetupFailure(h);
+    }
+
+    /**
+     * Triggers the UE initiated EPS fallback procedure.
+     *
+     * @param reason specifies the reason for EPS fallback.
+     * @param response is callback message.
+     */
+    public void triggerEpsFallback(@MmTelFeature.EpsFallbackReason int reason, Message response) {
+        mCi.triggerEpsFallback(reason, response);
+    }
+
+    /**
+     * Notifies the recommended bit rate for the indicated logical channel and direction.
+     *
+     * @param mediaType MediaType is used to identify media stream such as audio or video.
+     * @param direction Direction of this packet stream (e.g. uplink or downlink).
+     * @param bitsPerSecond The recommended bit rate for the UE for a specific logical channel and
+     *        a specific direction by NW.
+     */
+    public void triggerNotifyAnbr(int mediaType, int direction, int bitsPerSecond) {
+    }
+
+    /**
+     * Sets the emergency mode
+     *
+     * @param emcMode The radio emergency mode type.
+     * @param result Callback message.
+     */
+    public void setEmergencyMode(@EmergencyConstants.EmergencyMode int emcMode,
+            @Nullable Message result) {
+        mCi.setEmergencyMode(emcMode, result);
+    }
+
+    /**
+     * Triggers an emergency network scan.
+     *
+     * @param accessNetwork Contains the list of access network types to be prioritized
+     *        during emergency scan. The 1st entry has the highest priority.
+     * @param scanType Indicates the type of scans to be performed i.e. limited scan,
+     *        full service scan or any scan.
+     * @param result Callback message.
+     */
+    public void triggerEmergencyNetworkScan(
+            @NonNull @AccessNetworkConstants.RadioAccessNetworkType int[] accessNetwork,
+            @DomainSelectionService.EmergencyScanType int scanType, @Nullable Message result) {
+        mCi.triggerEmergencyNetworkScan(accessNetwork, scanType, result);
+    }
+
+    /**
+     * Cancels ongoing emergency network scan
+     * @param resetScan Indicates how the next {@link #triggerEmergencyNetworkScan} should work.
+     *        If {@code true}, then the modem shall start the new scan from the beginning,
+     *        otherwise the modem shall resume from the last search.
+     * @param result Callback message.
+     */
+    public void cancelEmergencyNetworkScan(boolean resetScan, @Nullable Message result) {
+        mCi.cancelEmergencyNetworkScan(resetScan, result);
+    }
+
+    /**
+     * Exits ongoing emergency mode
+     * @param result Callback message.
+     */
+    public void exitEmergencyMode(@Nullable Message result) {
+        mCi.exitEmergencyMode(result);
+    }
+
+    /**
+     * Registers for emergency network scan result.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForEmergencyNetworkScan(@NonNull Handler h,
+            int what, @Nullable Object obj) {
+        mCi.registerForEmergencyNetworkScan(h, what, obj);
+    }
+
+    /**
+     * Unregisters for emergency network scan result.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForEmergencyNetworkScan(@NonNull Handler h) {
+        mCi.unregisterForEmergencyNetworkScan(h);
+    }
+
+    /**
+     * Notifies that IMS deregistration is triggered.
+     *
+     * @param reason the reason why the deregistration is triggered.
+     */
+    public void triggerImsDeregistration(
+            @ImsRegistrationImplBase.ImsDeregistrationReason int reason) {
+        if (mImsPhone != null) {
+            mImsPhone.triggerImsDeregistration(reason);
+        }
+    }
+
+    /**
+     * Registers for the domain selected for emergency calls.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForEmergencyDomainSelected(
+            @NonNull Handler h, int what, @Nullable Object obj) {
+    }
+
+    /**
+     * Unregisters for the domain selected for emergency calls.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForEmergencyDomainSelected(@NonNull Handler h) {
+    }
+
+    /**
+     * Notifies the domain selected.
+     *
+     * @param transportType The preferred transport type.
+     */
+    public void notifyEmergencyDomainSelected(
+            @AccessNetworkConstants.TransportType int transportType) {
+    }
+
+    /**
+     * @return Telephony tester instance.
+     */
+    public @Nullable TelephonyTester getTelephonyTester() {
+        return mTelephonyTester;
+    }
+
+    /**
+     * @return User handle associated with the phone's subscription id. {@code null} if subscription
+     * is invalid or not found.
+     */
+    @Nullable
+    public UserHandle getUserHandle() {
+        int subId = getSubId();
+
+        UserHandle userHandle = null;
+        try {
+            SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class);
+            if (subManager != null) {
+                userHandle = subManager.getSubscriptionUserHandle(subId);
+            }
+        } catch (IllegalArgumentException ex) {
+            loge("getUserHandle: ex=" + ex);
+        }
+
+        return userHandle;
+    }
+
+    /**
+     * Checks if the context user is a managed profile.
+     *
+     * Note that this applies specifically to <em>managed</em> profiles.
+     *
+     * @return whether the context user is a managed profile.
+     */
+    public boolean isManagedProfile() {
+        UserHandle userHandle = getUserHandle();
+        UserManager userManager = mContext.getSystemService(UserManager.class);
+        if (userHandle == null || userManager == null) return false;
+        return userManager.isManagedProfile(userHandle.getIdentifier());
+    }
+
+    /**
+     * @return global null cipher and integrity enabled preference
+     */
+    public boolean getNullCipherAndIntegrityEnabledPreference() {
+        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
+        return sp.getBoolean(PREF_NULL_CIPHER_AND_INTEGRITY_ENABLED, true);
+    }
+
+    /**
+     * @return whether or not this Phone interacts with a modem that supports the null cipher
+     * and integrity feature.
+     */
+    public boolean isNullCipherAndIntegritySupported() {
+        return false;
+    }
+
+    /**
+     * Override to implement handling of an update to the enablement of the null cipher and
+     * integrity preference.
+     * {@see #PREF_NULL_CIPHER_AND_INTEGRITY_ENABLED}
+     */
+    public void handleNullCipherEnabledChange() {
+    }
+
+    /**
+     * Notifies the IMS call status to the modem.
+     *
+     * @param imsCallInfo The list of {@link ImsCallInfo}.
+     * @param response A callback to receive the response.
+     */
+    public void updateImsCallStatus(@NonNull List<ImsCallInfo> imsCallInfo, Message response) {
+        mCi.updateImsCallStatus(imsCallInfo, response);
+    }
+
+    /**
+     * Enables or disables N1 mode (access to 5G core network) in accordance with
+     * 3GPP TS 24.501 4.9.
+     * @param enable {@code true} to enable N1 mode, {@code false} to disable N1 mode.
+     * @param result Callback message to receive the result.
+     */
+    public void setN1ModeEnabled(boolean enable, Message result) {
+        mCi.setN1ModeEnabled(enable, result);
+    }
+
+    /**
+     * Check whether N1 mode (access to 5G core network) is enabled or not.
+     * @param result Callback message to receive the result.
+     */
+    public void isN1ModeEnabled(Message result) {
+        mCi.isN1ModeEnabled(result);
+    }
+
+    /**
+     * Return current cell broadcast ranges.
+     */
+    public List<CellBroadcastIdRange> getCellBroadcastIdRanges() {
+        return new ArrayList<>();
+    }
+
+    /**
+     * Set reception of cell broadcast messages with the list of the given ranges.
+     */
+    public void setCellBroadcastIdRanges(
+            @NonNull List<CellBroadcastIdRange> ranges, Consumer<Integer> callback) {
+        callback.accept(TelephonyManager.CELL_BROADCAST_RESULT_UNSUPPORTED);
+    }
+
+    /**
+     * Start receiving satellite position updates.
+     * This can be called by the pointing UI when the user starts pointing to the satellite.
+     * Modem should continue to report the pointing input as the device or satellite moves.
+     *
+     * @param result The Message to send to result of the operation to.
+     **/
+    public void startSatellitePositionUpdates(Message result) {
+        mCi.startSendingSatellitePointingInfo(result);
+    }
+
+    /**
+     * Stop receiving satellite position updates.
+     * This can be called by the pointing UI when the user stops pointing to the satellite.
+     *
+     * @param result The Message to send to result of the operation to.
+     **/
+    public void stopSatellitePositionUpdates(Message result) {
+        mCi.stopSendingSatellitePointingInfo(result);
+    }
+
+    /**
+     * Get maximum number of characters per text message on satellite.
+     * @param result The Message to send the result of the operation to.
+     */
+    public void getMaxCharactersPerSatelliteTextMessage(Message result) {
+        mCi.getMaxCharactersPerSatelliteTextMessage(result);
+    }
+
+    /**
+     * Power on or off the satellite modem.
+     * @param result The Message to send the result of the operation to.
+     * @param powerOn {@code true} to power on the satellite modem and {@code false} to power off.
+     */
+    public void setSatellitePower(Message result, boolean powerOn) {
+        mCi.setSatellitePower(result, powerOn);
+    }
+
+    /**
+     * Check whether the satellite modem is powered on.
+     * @param result The Message to send the result of the operation to.
+     */
+    public void isSatellitePowerOn(Message result) {
+        mCi.getSatellitePowerState(result);
+    }
+
+    /**
+     * Check whether the satellite service is supported on the device.
+     * @param result The Message to send the result of the operation to.
+     */
+    public void isSatelliteSupported(Message result) {
+        mCi.isSatelliteSupported(result);
+    }
+
+    /**
+     * Check whether the satellite modem is provisioned.
+     * @param result The Message to send the result of the operation to.
+     */
+    public void isSatelliteProvisioned(Message result) {
+        mCi.getSatelliteProvisionState(result);
+    }
+
+    /**
+     * Get the satellite capabilities.
+     * @param result The Message to send the result of the operation to.
+     */
+    public void getSatelliteCapabilities(Message result) {
+        mCi.getSatelliteCapabilities(result);
+    }
+
+    /**
+     * Registers for pointing info changed from satellite modem.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForSatellitePositionInfoChanged(@NonNull Handler h,
+            int what, @Nullable Object obj) {
+        //TODO: Rename CommandsInterface and other modules when updating HAL APIs.
+        mCi.registerForSatellitePointingInfoChanged(h, what, obj);
+    }
+
+    /**
+     * Unregisters for pointing info changed from satellite modem.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForSatellitePositionInfoChanged(@NonNull Handler h) {
+        //TODO: Rename CommandsInterface and other modules when updating HAL APIs.
+        mCi.unregisterForSatellitePointingInfoChanged(h);
+    }
+
+    /**
+     * Registers for datagrams delivered events from satellite modem.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForSatelliteDatagramsDelivered(@NonNull Handler h,
+            int what, @Nullable Object obj) {
+        //TODO: Remove.
+        mCi.registerForSatelliteMessagesTransferComplete(h, what, obj);
+    }
+
+    /**
+     * Unregisters for datagrams delivered events from satellite modem.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForSatelliteDatagramsDelivered(@NonNull Handler h) {
+        //TODO: Remove.
+        mCi.unregisterForSatelliteMessagesTransferComplete(h);
+    }
+
+    /**
+     * Provision the subscription with a satellite provider.
+     * This is needed to register the device/subscription if the provider allows dynamic
+     * registration.
+     *
+     * @param result Callback message to receive the result.
+     * @param token The token of the device/subscription to be provisioned.
+     */
+    public void provisionSatelliteService(Message result, String token) {
+        // TODO: update parameters in HAL
+        // mCi.provisionSatelliteService(result, token);
+    }
+
+    /**
+     * Deprovision the device/subscription with a satellite provider.
+     * This is needed to unregister the device/subscription if the provider allows dynamic
+     * registration.
+     * If provisioning is in progress for the given SIM, cancel the request.
+     * If there is no request in progress, deprovision the given SIM.
+     *
+     * @param result Callback message to receive the result.
+     * @param token The token of the device/subscription to be deprovisioned.
+     */
+    public void deprovisionSatelliteService(Message result, String token) {
+        //TODO (b/266126070): add implementation.
+    }
+
+    /**
+     * Register for a satellite provision state changed event.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForSatelliteProvisionStateChanged(Handler h, int what, Object obj) {
+        mCi.registerForSatelliteProvisionStateChanged(h, what, obj);
+    }
+
+    /**
+     * Unregister for a satellite provision state changed event.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForSatelliteProvisionStateChanged(Handler h) {
+        mCi.unregisterForSatelliteProvisionStateChanged(h);
+    }
+
+    /**
+     * Get the list of provisioned satellite features.
+     *
+     * @param result Callback message to receive the result.
+     */
+    public void getProvisionedSatelliteFeatures(Message result) {
+        //TODO (b/266126070): add implementation.
+    }
+
+    /**
+     * Registers for satellite state changed from satellite modem.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForSatelliteModemStateChanged(@NonNull Handler h, int what,
+            @Nullable Object obj) {
+        mCi.registerForSatelliteModeChanged(h, what, obj);
+    }
+
+    /**
+     * Unregisters for satellite state changed from satellite modem.
+     *
+     * @param h Handler to be removed from registrant list.
+     */
+    public void unregisterForSatelliteModemStateChanged(@NonNull Handler h) {
+        mCi.unregisterForSatelliteModeChanged(h);
+    }
+
+    /**
+     * Registers for pending datagram count info from satellite modem.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForPendingDatagramCount(@NonNull Handler h, int what,
+            @Nullable Object obj) {
+        mCi.registerForPendingSatelliteMessageCount(h, what, obj);
+    }
+
+    /**
+     * Unregisters for pending datagram count info from satellite modem.
+     *
+     * @param h Handler to be removed from registrant list.
+     */
+    public void unregisterForPendingDatagramCount(@NonNull Handler h) {
+        mCi.unregisterForPendingSatelliteMessageCount(h);
+    }
+
+    /**
+     * Register to receive incoming datagrams over satellite.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForSatelliteDatagramsReceived(@NonNull Handler h, int what,
+            @Nullable Object obj) {
+        // TODO: rename
+        mCi.registerForNewSatelliteMessages(h, what, obj);
+    }
+
+    /**
+     * Unregister to stop receiving incoming datagrams over satellite.
+     *
+     * @param h Handler to be removed from registrant list.
+     */
+    public void unregisterForSatelliteDatagramsReceived(@NonNull Handler h) {
+        // TODO: rename
+        mCi.unregisterForNewSatelliteMessages(h);
+    }
+
+    /**
+     * Poll pending datagrams over satellite.
+     * @param result The Message to send the result of the operation to.
+     */
+    public void pollPendingSatelliteDatagrams(Message result) {
+        //mCi.pollPendingSatelliteDatagrams(result);
+    }
+
+    /**
+     * Send datagram over satellite.
+     * @param result The Message to send the result of the operation to.
+     * @param datagram Datagram to send over satellite.
+     * @param needFullScreenPointingUI this is used to indicate pointingUI app to open in
+     *                                 full screen mode.
+     */
+    public void sendSatelliteDatagram(Message result, SatelliteDatagram datagram,
+            boolean needFullScreenPointingUI) {
+        //mCi.sendSatelliteDatagram(result, datagram);
+    }
+
+    /**
+     * Check whether satellite communication is allowed for the current location.
+     * @param result The Message to send the result of the operation to.
+     */
+    public void isSatelliteCommunicationAllowedForCurrentLocation(Message result) {
+        mCi.isSatelliteCommunicationAllowedForCurrentLocation(result);
+    }
+
+    /**
+     * Get the time after which the satellite will be visible.
+     * @param result The Message to send the result of the operation to.
+     */
+    public void requestTimeForNextSatelliteVisibility(Message result) {
+        mCi.getTimeForNextSatelliteVisibility(result);
+    }
+
+    /**
+     * Start callback mode
+     * @param type for callback mode entry.
+     */
+    public void startCallbackMode(@TelephonyManager.EmergencyCallbackModeType int type) {
+        Rlog.d(LOG_TAG, "startCallbackMode:type=" + type);
+        mNotifier.notifyCallbackModeStarted(this, type);
+    }
+
+    /**
+     * Stop callback mode
+     * @param type for callback mode exit.
+     * @param reason for stopping callback mode.
+     */
+    public void stopCallbackMode(@TelephonyManager.EmergencyCallbackModeType int type,
+            @TelephonyManager.EmergencyCallbackModeStopReason int reason) {
+        Rlog.d(LOG_TAG, "stopCallbackMode:type=" + type + ", reason=" + reason);
+        mNotifier.notifyCallbackModeStopped(this, type, reason);
     }
 
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -4926,7 +5530,7 @@
         pw.println(" isDnsCheckDisabled()=" + isDnsCheckDisabled());
         pw.println(" getUnitTestMode()=" + getUnitTestMode());
         pw.println(" getState()=" + getState());
-        pw.println(" getIccSerialNumber()=" + getIccSerialNumber());
+        pw.println(" getIccSerialNumber()=" + Rlog.pii(LOG_TAG, getIccSerialNumber()));
         pw.println(" getIccRecordsLoaded()=" + getIccRecordsLoaded());
         pw.println(" getMessageWaitingIndicator()=" + getMessageWaitingIndicator());
         pw.println(" getCallForwardingIndicator()=" + getCallForwardingIndicator());
diff --git a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
index 081e7a0..8b95824 100644
--- a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
+++ b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
@@ -72,6 +72,7 @@
     private TelephonyManager mTelephonyManager;
     private static final RegistrantList sMultiSimConfigChangeRegistrants = new RegistrantList();
     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);
     /**
      * Init method to instantiate the object
@@ -123,6 +124,21 @@
         }
     }
 
+    // If virtual DSDA is enabled for this UE, then updates maxActiveVoiceSubscriptions to 2.
+    private PhoneCapability maybeUpdateMaxActiveVoiceSubscriptions(
+            final PhoneCapability staticCapability) {
+        boolean enableVirtualDsda = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_enable_virtual_dsda);
+
+        if (staticCapability.getLogicalModemList().size() > 1 && enableVirtualDsda) {
+            return new PhoneCapability.Builder(staticCapability)
+                    .setMaxActiveVoiceSubscriptions(2)
+                    .build();
+        } else {
+            return staticCapability;
+        }
+    }
+
     /**
      * Static method to get instance.
      */
@@ -181,6 +197,8 @@
                     ar = (AsyncResult) msg.obj;
                     if (ar != null && ar.exception == null) {
                         mStaticCapability = (PhoneCapability) ar.result;
+                        mStaticCapability =
+                                maybeUpdateMaxActiveVoiceSubscriptions(mStaticCapability);
                         notifyCapabilityChanged();
                     } else {
                         log(msg.what + " failure. Not getting phone capability." + ar.exception);
@@ -367,11 +385,7 @@
             // eg if we are going from 2 phones to 1 phone, we need to deregister RIL for the
             // second phone. This loop does nothing if numOfActiveModems is increasing.
             for (int phoneId = numOfActiveModems; phoneId < oldNumOfActiveModems; phoneId++) {
-                if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-                    SubscriptionManagerService.getInstance().markSubscriptionsInactive(phoneId);
-                } else {
-                    SubscriptionController.getInstance().clearSubInfoRecord(phoneId);
-                }
+                SubscriptionManagerService.getInstance().markSubscriptionsInactive(phoneId);
                 subInfoCleared = true;
                 mPhones[phoneId].mCi.onSlotActiveStatusChange(
                         SubscriptionManager.isValidPhoneId(phoneId));
@@ -405,13 +419,8 @@
                         + "setting VOICE & SMS subId to -1 (No Preference)");
 
                 //Set the default VOICE subId to -1 ("No Preference")
-                if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-                    SubscriptionManagerService.getInstance().setDefaultVoiceSubId(
-                            SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-                } else {
-                    SubscriptionController.getInstance().setDefaultVoiceSubId(
-                            SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-                }
+                SubscriptionManagerService.getInstance().setDefaultVoiceSubId(
+                        SubscriptionManager.INVALID_SUBSCRIPTION_ID);
 
                 //TODO:: Set the default SMS sub to "No Preference". Tracking this bug (b/227386042)
             } else {
@@ -475,34 +484,46 @@
      * @return true if the modem service is set successfully, false otherwise.
      */
     public boolean setModemService(String serviceName) {
-        if (mRadioConfig == null || mPhones[0] == null) {
-            return false;
-        }
-
         log("setModemService: " + serviceName);
         boolean statusRadioConfig = false;
         boolean statusRil = false;
         final boolean isAllowed = SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false);
+        final boolean isAllowedForBoot =
+                SystemProperties.getBoolean(BOOT_ALLOW_MOCK_MODEM_PROPERTY, false);
 
-        // Check for ALLOW_MOCK_MODEM_PROPERTY on user builds
-        if (isAllowed || DEBUG) {
-            if (serviceName != null) {
+        // Check for ALLOW_MOCK_MODEM_PROPERTY and BOOT_ALLOW_MOCK_MODEM_PROPERTY on user builds
+        if (isAllowed || isAllowedForBoot || DEBUG) {
+            if (mRadioConfig != null) {
                 statusRadioConfig = mRadioConfig.setModemService(serviceName);
-
-                //TODO: consider multi-sim case (b/210073692)
-                statusRil = mPhones[0].mCi.setModemService(serviceName);
-            } else {
-                statusRadioConfig = mRadioConfig.setModemService(null);
-
-                //TODO: consider multi-sim case
-                statusRil = mPhones[0].mCi.setModemService(null);
             }
 
-            return statusRadioConfig && statusRil;
+            if (!statusRadioConfig) {
+                loge("setModemService: switching modem service for radioconfig fail");
+                return false;
+            }
+
+            for (int i = 0; i < getPhoneCount(); i++) {
+                if (mPhones[i] != null) {
+                    statusRil = mPhones[i].mCi.setModemService(serviceName);
+                }
+
+                if (!statusRil) {
+                    loge("setModemService: switch modem for radio " + i + " fail");
+
+                    // Disconnect the switched service
+                    mRadioConfig.setModemService(null);
+                    for (int t = 0; t < i; t++) {
+                        mPhones[t].mCi.setModemService(null);
+                    }
+                    return false;
+                }
+            }
         } else {
             loge("setModemService is not allowed");
             return false;
         }
+
+        return true;
     }
 
      /**
@@ -510,7 +531,6 @@
      * @return the service name of the connected service.
      */
     public String getModemService() {
-        //TODO: consider multi-sim case
         if (mPhones[0] == null) {
             return "";
         }
diff --git a/src/java/com/android/internal/telephony/PhoneFactory.java b/src/java/com/android/internal/telephony/PhoneFactory.java
index 8220ee3..57a375b 100644
--- a/src/java/com/android/internal/telephony/PhoneFactory.java
+++ b/src/java/com/android/internal/telephony/PhoneFactory.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony;
 
+import static android.telephony.TelephonyManager.HAL_SERVICE_RADIO;
+
 import static com.android.internal.telephony.PhoneConstants.PHONE_TYPE_CDMA;
 import static com.android.internal.telephony.PhoneConstants.PHONE_TYPE_CDMA_LTE;
 
@@ -29,10 +31,8 @@
 import android.content.pm.PackageManager;
 import android.net.LocalServerSocket;
 import android.os.Build;
-import android.os.HandlerThread;
 import android.os.Looper;
 import android.preference.PreferenceManager;
-import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.telephony.AnomalyReporter;
@@ -87,8 +87,6 @@
     private static @Nullable EuiccCardController sEuiccCardController;
     private static SubscriptionManagerService sSubscriptionManagerService;
 
-    static private SubscriptionInfoUpdater sSubInfoRecordUpdater = null;
-
     @UnsupportedAppUsage
     static private boolean sMadeDefaults = false;
     @UnsupportedAppUsage
@@ -105,8 +103,6 @@
     private static MetricsCollector sMetricsCollector;
     private static RadioInterfaceCapabilityController sRadioHalCapabilities;
 
-    private static boolean sSubscriptionManagerServiceEnabled = false;
-
     //***** Class Methods
 
     public static void makeDefaultPhones(Context context) {
@@ -123,12 +119,6 @@
             if (!sMadeDefaults) {
                 sContext = context;
 
-                // This is a temp flag which will be removed before U AOSP public release.
-                sSubscriptionManagerServiceEnabled = context.getResources().getBoolean(
-                        com.android.internal.R.bool.config_using_subscription_manager_service)
-                        || DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TELEPHONY,
-                        "enable_subscription_manager_service", false);
-
                 // create the telephony device controller.
                 TelephonyDevController.create();
 
@@ -191,7 +181,7 @@
 
                 if (numPhones > 0) {
                     final RadioConfig radioConfig = RadioConfig.make(context,
-                            sCommandsInterfaces[0].getHalVersion());
+                            sCommandsInterfaces[0].getHalVersion(HAL_SERVICE_RADIO));
                     sRadioHalCapabilities = RadioInterfaceCapabilityController.init(radioConfig,
                             sCommandsInterfaces[0]);
                 } else {
@@ -206,24 +196,12 @@
                 // call getInstance()
                 sUiccController = UiccController.make(context);
 
-
-                if (isSubscriptionManagerServiceEnabled()) {
-                    Rlog.i(LOG_TAG, "Creating SubscriptionManagerService");
-                    sSubscriptionManagerService = new SubscriptionManagerService(context,
-                            Looper.myLooper());
-                } else {
-                    Rlog.i(LOG_TAG, "Creating SubscriptionController");
-                    TelephonyComponentFactory.getInstance().inject(SubscriptionController.class
-                            .getName()).initSubscriptionController(context);
-                }
-
-                SubscriptionController sc = null;
-                if (!isSubscriptionManagerServiceEnabled()) {
-                    sc = SubscriptionController.getInstance();
-                }
+                Rlog.i(LOG_TAG, "Creating SubscriptionManagerService");
+                sSubscriptionManagerService = new SubscriptionManagerService(context,
+                        Looper.myLooper());
 
                 TelephonyComponentFactory.getInstance().inject(MultiSimSettingController.class.
-                        getName()).initMultiSimSettingController(context, sc);
+                        getName()).initMultiSimSettingController(context);
 
                 if (context.getPackageManager().hasSystemFeature(
                         PackageManager.FEATURE_TELEPHONY_EUICC)) {
@@ -255,16 +233,6 @@
 
                 sMadeDefaults = true;
 
-                if (!isSubscriptionManagerServiceEnabled()) {
-                    Rlog.i(LOG_TAG, "Creating SubInfoRecordUpdater ");
-                    HandlerThread pfhandlerThread = new HandlerThread("PhoneFactoryHandlerThread");
-                    pfhandlerThread.start();
-                    sSubInfoRecordUpdater = TelephonyComponentFactory.getInstance().inject(
-                            SubscriptionInfoUpdater.class.getName())
-                            .makeSubscriptionInfoUpdater(pfhandlerThread.getLooper(), context,
-                                    SubscriptionController.getInstance());
-                }
-
                 // Only bring up IMS if the device supports having an IMS stack.
                 if (context.getPackageManager().hasSystemFeature(
                         PackageManager.FEATURE_TELEPHONY_IMS)) {
@@ -304,14 +272,6 @@
     }
 
     /**
-     * @return {@code true} if the new {@link SubscriptionManagerService} is enabled, otherwise the
-     * old {@link SubscriptionController} is used.
-     */
-    public static boolean isSubscriptionManagerServiceEnabled() {
-        return sSubscriptionManagerServiceEnabled;
-    }
-
-    /**
      * Upon single SIM to dual SIM switch or vice versa, we dynamically allocate or de-allocate
      * Phone and CommandInterface objects.
      * @param context
@@ -410,10 +370,6 @@
         }
     }
 
-    public static SubscriptionInfoUpdater getSubscriptionInfoUpdater() {
-        return sSubInfoRecordUpdater;
-    }
-
     /**
      * Get the network factory associated with a given phone ID.
      * @param phoneId the phone id
@@ -450,7 +406,6 @@
      * @param phoneId The phone's id.
      * @return the preferred network mode bitmask that should be set.
      */
-    // TODO: Fix when we "properly" have TelephonyDevController/SubscriptionController ..
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public static int calculatePreferredNetworkType(int phoneId) {
         if (getPhone(phoneId) == null) {
@@ -467,10 +422,7 @@
     /* Gets the default subscription */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public static int getDefaultSubscription() {
-        if (isSubscriptionManagerServiceEnabled()) {
-            return SubscriptionManagerService.getInstance().getDefaultSubId();
-        }
-        return SubscriptionController.getInstance().getDefaultSubId();
+        return SubscriptionManagerService.getInstance().getDefaultSubId();
     }
 
     /* Returns User SMS Prompt property,  enabled or not */
@@ -498,19 +450,7 @@
     }
 
     /**
-     * Request a refresh of the embedded subscription list.
-     *
-     * @param cardId the card ID of the eUICC.
-     * @param callback Optional callback to execute after the refresh completes. Must terminate
-     *     quickly as it will be called from SubscriptionInfoUpdater's handler thread.
-     */
-    public static void requestEmbeddedSubscriptionInfoListRefresh(
-            int cardId, @Nullable Runnable callback) {
-        sSubInfoRecordUpdater.requestEmbeddedSubscriptionInfoListRefresh(cardId, callback);
-    }
-
-    /**
-     * Get a the SmsController.
+     * Get the instance of {@link SmsController}.
      */
     public static SmsController getSmsController() {
         synchronized (sLockProxyPhones) {
@@ -613,25 +553,16 @@
         pw.decreaseIndent();
         pw.println("++++++++++++++++++++++++++++++++");
 
-        if (!isSubscriptionManagerServiceEnabled()) {
-            pw.println("SubscriptionController:");
-            pw.increaseIndent();
-            try {
-                SubscriptionController.getInstance().dump(fd, pw, args);
-            } catch (Exception e) {
-                e.printStackTrace();
-            }
-            pw.flush();
-            pw.decreaseIndent();
-            pw.println("++++++++++++++++++++++++++++++++");
+        pw.flush();
+        pw.decreaseIndent();
+        pw.println("++++++++++++++++++++++++++++++++");
 
-            pw.println("SubInfoRecordUpdater:");
-            pw.increaseIndent();
-            try {
-                sSubInfoRecordUpdater.dump(fd, pw, args);
-            } catch (Exception e) {
-                e.printStackTrace();
-            }
+        pw.println("sRadioHalCapabilities:");
+        pw.increaseIndent();
+        try {
+            sRadioHalCapabilities.dump(fd, pw, args);
+        } catch (Exception e) {
+            e.printStackTrace();
         }
         pw.flush();
         pw.decreaseIndent();
diff --git a/src/java/com/android/internal/telephony/PhoneInternalInterface.java b/src/java/com/android/internal/telephony/PhoneInternalInterface.java
index 5b4d5e5..32c0c73 100644
--- a/src/java/com/android/internal/telephony/PhoneInternalInterface.java
+++ b/src/java/com/android/internal/telephony/PhoneInternalInterface.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.telephony;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
@@ -36,9 +35,9 @@
 
 import com.android.internal.telephony.PhoneConstants.DataState;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.function.Consumer;
 
 /**
@@ -211,16 +210,6 @@
     static final String REASON_DATA_UNTHROTTLED = "dataUnthrottled";
     static final String REASON_TRAFFIC_DESCRIPTORS_UPDATED = "trafficDescriptorsUpdated";
 
-    // Reasons for Radio being powered off
-    int RADIO_POWER_REASON_USER = 0;
-    int RADIO_POWER_REASON_THERMAL = 1;
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = {"RADIO_POWER_REASON_"},
-        value = {
-            RADIO_POWER_REASON_USER,
-            RADIO_POWER_REASON_THERMAL})
-    public @interface RadioPowerReason {}
-
     // Used for band mode selection methods
     static final int BM_UNSPECIFIED = RILConstants.BAND_MODE_UNSPECIFIED; // automatic
     static final int BM_EURO_BAND   = RILConstants.BAND_MODE_EURO;
@@ -593,8 +582,9 @@
      * getServiceState().getState() will not change immediately after this call.
      * registerForServiceStateChanged() to find out when the
      * request is complete. This will set the reason for radio power state as {@link
-     * #RADIO_POWER_REASON_USER}. This will not guarantee that the requested radio power state will
-     * actually be set. See {@link #setRadioPowerForReason(boolean, boolean, boolean, boolean, int)}
+     * android.telephony.TelephonyManager#RADIO_POWER_REASON_USER}. This will not guarantee that the
+     * requested radio power state will actually be set.
+     * See {@link #setRadioPowerForReason(boolean, boolean, boolean, boolean, int)}
      * for details.
      *
      * @param power true means "on", false means "off".
@@ -620,8 +610,9 @@
      * getServiceState().getState() will not change immediately after this call.
      * registerForServiceStateChanged() to find out when the
      * request is complete. This will set the reason for radio power state as {@link
-     * #RADIO_POWER_REASON_USER}. This will not guarantee that the requested radio power state will
-     * actually be set. See {@link #setRadioPowerForReason(boolean, boolean, boolean, boolean, int)}
+     * android.telephony.TelephonyManager#RADIO_POWER_REASON_USER}. This will not guarantee that the
+     * requested radio power state will actually be set.
+     * See {@link #setRadioPowerForReason(boolean, boolean, boolean, boolean, int)}
      * for details.
      *
      * @param power true means "on", false means "off".
@@ -638,7 +629,7 @@
     default void setRadioPower(boolean power, boolean forEmergencyCall,
             boolean isSelectedPhoneForEmergencyCall, boolean forceApply) {
         setRadioPowerForReason(power, forEmergencyCall, isSelectedPhoneForEmergencyCall, forceApply,
-                RADIO_POWER_REASON_USER);
+                TelephonyManager.RADIO_POWER_REASON_USER);
     }
 
     /**
@@ -656,11 +647,19 @@
      * @param power true means "on", false means "off".
      * @param reason RadioPowerReason constant defining the reason why the radio power was set.
      */
-    default void setRadioPowerForReason(boolean power, @RadioPowerReason int reason) {
+    default void setRadioPowerForReason(boolean power,
+            @TelephonyManager.RadioPowerReason int reason) {
         setRadioPowerForReason(power, false, false, false, reason);
     }
 
     /**
+     * @return reasons for powering off radio.
+     */
+    default Set<Integer> getRadioPowerOffReasons() {
+        return new HashSet<>();
+    }
+
+    /**
      * Sets the radio power on/off state with option to specify whether it's for emergency call
      * (off is sometimes called "airplane mode") and option to set the reason for setting the power
      * state. Current state can be gotten via {@link #getServiceState()}.
@@ -686,7 +685,7 @@
      */
     default void setRadioPowerForReason(boolean power, boolean forEmergencyCall,
             boolean isSelectedPhoneForEmergencyCall, boolean forceApply,
-            @RadioPowerReason int reason) {}
+            @TelephonyManager.RadioPowerReason int reason) {}
 
     /**
      * Get the line 1 phone number (MSISDN). For CDMA phones, the MDN is returned
@@ -1036,6 +1035,10 @@
     String getImei();
 
     /**
+     * Retrieves IMEI type for phones.
+     */
+    int getImeiType();
+    /**
      * Retrieves the IccPhoneBookInterfaceManager of the Phone
      */
     public IccPhoneBookInterfaceManager getIccPhoneBookInterfaceManager();
diff --git a/src/java/com/android/internal/telephony/PhoneNotifier.java b/src/java/com/android/internal/telephony/PhoneNotifier.java
index 701a157..20d6702 100644
--- a/src/java/com/android/internal/telephony/PhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/PhoneNotifier.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.telephony.Annotation;
 import android.telephony.Annotation.RadioPowerState;
 import android.telephony.Annotation.SrvccState;
 import android.telephony.BarringInfo;
@@ -31,8 +32,11 @@
 import android.telephony.ServiceState;
 import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager.DataEnabledReason;
+import android.telephony.TelephonyManager.EmergencyCallbackModeStopReason;
+import android.telephony.TelephonyManager.EmergencyCallbackModeType;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.MediaQualityStatus;
 
 import java.util.List;
 
@@ -78,7 +82,10 @@
 
     void notifyCellInfo(Phone sender, List<CellInfo> cellInfo);
 
-    void notifyPreciseCallState(Phone sender);
+    /** Send a notification that precise call state changed. */
+    void notifyPreciseCallState(Phone sender, String[] imsCallIds,
+            @Annotation.ImsCallServiceType int[] imsCallServiceTypes,
+            @Annotation.ImsCallType int[] imsCallTypes);
 
     void notifyDisconnectCause(Phone sender, int cause, int preciseCause);
 
@@ -113,6 +120,9 @@
     /** Notify of a change to the call quality of an active foreground call. */
     void notifyCallQualityChanged(Phone sender, CallQuality callQuality, int callNetworkType);
 
+    /** Notify of a change to the media quality status of an active foreground call. */
+    void notifyMediaQualityStatusChanged(Phone sender, MediaQualityStatus status);
+
     /** Notify registration failed */
     void notifyRegistrationFailed(Phone sender, @NonNull CellIdentity cellIdentity,
             @NonNull String chosenPlmn, int domain, int causeCode, int additionalCauseCode);
@@ -132,4 +142,11 @@
     /** Notify link capacity estimate has changed. */
     void notifyLinkCapacityEstimateChanged(Phone sender,
             List<LinkCapacityEstimate> linkCapacityEstimateList);
+
+    /** Notify callback mode started. */
+    void notifyCallbackModeStarted(Phone sender, @EmergencyCallbackModeType int type);
+
+    /** Notify callback mode stopped. */
+    void notifyCallbackModeStopped(Phone sender, @EmergencyCallbackModeType int type,
+            @EmergencyCallbackModeStopReason int reason);
 }
diff --git a/src/java/com/android/internal/telephony/PhoneSubInfoController.java b/src/java/com/android/internal/telephony/PhoneSubInfoController.java
index 8048eba..d30a73c 100644
--- a/src/java/com/android/internal/telephony/PhoneSubInfoController.java
+++ b/src/java/com/android/internal/telephony/PhoneSubInfoController.java
@@ -27,6 +27,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 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;
@@ -41,10 +42,14 @@
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.uicc.IsimRecords;
+import com.android.internal.telephony.uicc.SIMRecords;
 import com.android.internal.telephony.uicc.UiccCardApplication;
 import com.android.internal.telephony.uicc.UiccPort;
 import com.android.telephony.Rlog;
 
+import java.util.ArrayList;
+import java.util.List;
+
 public class PhoneSubInfoController extends IPhoneSubInfo.Stub {
     private static final String TAG = "PhoneSubInfoController";
     private static final boolean DBG = true;
@@ -154,13 +159,8 @@
         long identity = Binder.clearCallingIdentity();
         boolean isActive;
         try {
-            if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-                isActive = SubscriptionManagerService.getInstance().isActiveSubId(subId,
-                        callingPackage, callingFeatureId);
-            } else {
-                isActive = SubscriptionController.getInstance().isActiveSubId(subId, callingPackage,
-                        callingFeatureId);
-            }
+            isActive = SubscriptionManagerService.getInstance().isActiveSubId(subId,
+                    callingPackage, callingFeatureId);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -174,15 +174,12 @@
             }
             identity = Binder.clearCallingIdentity();
             try {
-                if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-                    SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
-                            .getSubscriptionInfoInternal(subId);
-                    if (subInfo != null && !TextUtils.isEmpty(subInfo.getImsi())) {
-                        return subInfo.getImsi();
-                    }
-                    return null;
+                SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
+                        .getSubscriptionInfoInternal(subId);
+                if (subInfo != null && !TextUtils.isEmpty(subInfo.getImsi())) {
+                    return subInfo.getImsi();
                 }
-                return SubscriptionController.getInstance().getImsiPrivileged(subId);
+                return null;
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
@@ -351,6 +348,34 @@
     }
 
     /**
+     * Fetches the IMS private user identity (EF_IMPI) based on subscriptionId.
+     *
+     * @param subId subscriptionId
+     * @return IMPI (IMS private user identity) of type string.
+     * @throws IllegalArgumentException if the subscriptionId is not valid
+     * @throws IllegalStateException in case the ISIM hasn’t been loaded.
+     * @throws SecurityException if the caller does not have the required permission
+     */
+    public String getImsPrivateUserIdentity(int subId, String callingPackage,
+            String callingFeatureId) {
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            throw new IllegalArgumentException("Invalid SubscriptionID  = " + subId);
+        }
+        if (!TelephonyPermissions.checkCallingOrSelfUseIccAuthWithDeviceIdentifier(mContext,
+                callingPackage, callingFeatureId, "getImsPrivateUserIdentity")) {
+            throw (new SecurityException("No permissions to the caller"));
+        }
+        Phone phone = getPhone(subId);
+        assert phone != null;
+        IsimRecords isim = phone.getIsimRecords();
+        if (isim != null) {
+            return isim.getIsimImpi();
+        } else {
+            throw new IllegalStateException("ISIM is not loaded");
+        }
+    }
+
+    /**
     * get the Isim Domain based on subId
     */
     public String getIsimDomain(int subId) {
@@ -381,6 +406,42 @@
     }
 
     /**
+     * Fetches the ISIM public user identities (EF_IMPU) from UICC based on subId
+     *
+     * @param subId subscriptionId
+     * @param callingPackage package name of the caller
+     * @param callingFeatureId feature Id of the caller
+     * @return List of public user identities of type android.net.Uri or empty list  if
+     * EF_IMPU is not available.
+     * @throws IllegalArgumentException if the subscriptionId is not valid
+     * @throws IllegalStateException in case the ISIM hasn’t been loaded.
+     * @throws SecurityException if the caller does not have the required permission
+     */
+    public List<Uri> getImsPublicUserIdentities(int subId, String callingPackage,
+            String callingFeatureId) {
+        if (TelephonyPermissions.
+                checkCallingOrSelfReadPrivilegedPhoneStatePermissionOrReadPhoneNumber(
+                mContext, subId, callingPackage, callingFeatureId, "getImsPublicUserIdentities")) {
+            Phone phone = getPhone(subId);
+            assert phone != null;
+            IsimRecords isimRecords = phone.getIsimRecords();
+            if (isimRecords != null) {
+                String[] impus = isimRecords.getIsimImpu();
+                List<Uri> impuList = new ArrayList<>();
+                for (String impu : impus) {
+                    if (impu != null && impu.trim().length() > 0) {
+                        impuList.add(Uri.parse(impu));
+                    }
+                }
+                return impuList;
+            }
+            throw new IllegalStateException("ISIM is not loaded");
+        } else {
+            throw new IllegalArgumentException("Invalid SubscriptionID  = " + subId);
+        }
+    }
+
+    /**
     * get the Isim Ist based on subId
     */
     public String getIsimIst(int subId) throws RemoteException {
@@ -410,6 +471,29 @@
                 });
     }
 
+    /**
+     * Returns the USIM service table that fetched from EFUST elementary field that are loaded
+     * based on the appType.
+     */
+    public String getSimServiceTable(int subId, int appType) throws RemoteException {
+        return callPhoneMethodForSubIdWithPrivilegedCheck(subId, "getSimServiceTable",
+                (phone) -> {
+                    UiccPort uiccPort = phone.getUiccPort();
+                    if (uiccPort == null || uiccPort.getUiccProfile() == null) {
+                        loge("getSimServiceTable(): uiccPort or uiccProfile is null");
+                        return null;
+                    }
+                    UiccCardApplication uiccApp = uiccPort.getUiccProfile().getApplicationByType(
+                            appType);
+                    if (uiccApp == null) {
+                        loge("getSimServiceTable(): no app with specified apptype="
+                                + appType);
+                        return null;
+                    }
+                    return ((SIMRecords)uiccApp.getIccRecords()).getSimServiceTable();
+                });
+    }
+
     @Override
     public String getIccSimChallengeResponse(int subId, int appType, int authType, String data,
             String callingPackage, String callingFeatureId) throws RemoteException {
@@ -430,7 +514,9 @@
             }
 
             if (authType != UiccCardApplication.AUTH_CONTEXT_EAP_SIM
-                    && authType != UiccCardApplication.AUTH_CONTEXT_EAP_AKA) {
+                    && authType != UiccCardApplication.AUTH_CONTEXT_EAP_AKA
+                    && authType != UiccCardApplication.AUTH_CONTEXT_GBA_BOOTSTRAP
+                    && authType != UiccCardApplication.AUTHTYPE_GBA_NAF_KEY_EXTERNAL) {
                 loge("getIccSimChallengeResponse() unsupported authType: " + authType);
                 return null;
             }
@@ -483,7 +569,7 @@
             if (phone != null) {
                 return callMethodHelper.callMethod(phone);
             } else {
-                loge(message + " phone is null for Subscription:" + subId);
+                if (VDBG) loge(message + " phone is null for Subscription:" + subId);
                 return null;
             }
         } finally {
@@ -573,6 +659,37 @@
         }
     }
 
+    /**
+     * Returns SIP URI or tel URI of the Public Service Identity of the SM-SC fetched from
+     * EF_PSISMSC elementary field as defined in Section 4.5.9 (3GPP TS 31.102).
+     * @throws IllegalStateException in case if phone or UiccApplication is not available.
+     */
+    public Uri getSmscIdentity(int subId, int appType) throws RemoteException {
+        Uri smscIdentityUri = callPhoneMethodForSubIdWithPrivilegedCheck(subId, "getSmscIdentity",
+                (phone) -> {
+                    try {
+                        String smscIdentity = null;
+                        UiccPort uiccPort = phone.getUiccPort();
+                        UiccCardApplication uiccApp =
+                                uiccPort.getUiccProfile().getApplicationByType(
+                                        appType);
+                        smscIdentity = (uiccApp != null) ? uiccApp.getIccRecords().getSmscIdentity()
+                                : null;
+                        if (TextUtils.isEmpty(smscIdentity)) {
+                            return Uri.EMPTY;
+                        }
+                        return Uri.parse(smscIdentity);
+                    } catch (NullPointerException ex) {
+                        Rlog.e(TAG, "getSmscIdentity(): Exception = " + ex);
+                        return null;
+                    }
+                });
+        if (smscIdentityUri == null) {
+            throw new IllegalStateException("Telephony service error");
+        }
+        return smscIdentityUri;
+    }
+
     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 498953c..ed9982e 100644
--- a/src/java/com/android/internal/telephony/ProxyController.java
+++ b/src/java/com/android/internal/telephony/ProxyController.java
@@ -47,8 +47,10 @@
     @VisibleForTesting
     static final int EVENT_START_RC_RESPONSE                = 2;
     private static final int EVENT_APPLY_RC_RESPONSE        = 3;
-    private static final int EVENT_FINISH_RC_RESPONSE       = 4;
-    private static final int EVENT_TIMEOUT                  = 5;
+    @VisibleForTesting
+    public static final int EVENT_FINISH_RC_RESPONSE        = 4;
+    @VisibleForTesting
+    public static final int EVENT_TIMEOUT                   = 5;
     @VisibleForTesting
     public static final int EVENT_MULTI_SIM_CONFIG_CHANGED  = 6;
 
@@ -212,6 +214,7 @@
         clearTransaction();
 
         // Keep a wake lock until we finish radio capability changed
+        logd("Acquiring wake lock for setting radio capability");
         mWakeLock.acquire();
 
         return doSetRadioCapabilities(rafs);
@@ -357,19 +360,25 @@
                 }
             }
             RadioCapability rc = (RadioCapability) ((AsyncResult) msg.obj).result;
-            if ((rc == null) || (rc.getSession() != mRadioCapabilitySessionId)) {
+            // Added exception condition  to continue to mark as transaction fail case.
+            // Checking session validity during exception is not valid
+            if (ar.exception == null
+                    && ((rc == null) || (rc.getSession() != mRadioCapabilitySessionId))) {
                 logd("onStartRadioCapabilityResponse: Ignore session=" + mRadioCapabilitySessionId
                         + " rc=" + rc);
                 return;
             }
             mRadioAccessFamilyStatusCounter--;
-            int id = rc.getPhoneId();
+            //rc.getPhoneId() moved to avoid Null Pointer Exception, since when exception occurs
+            //its expected rc is null.
             if (ar.exception != null) {
-                logd("onStartRadioCapabilityResponse: Error response session=" + rc.getSession());
-                logd("onStartRadioCapabilityResponse: phoneId=" + id + " status=FAIL");
-                mSetRadioAccessFamilyStatus[id] = SET_RC_STATUS_FAIL;
+                logd("onStartRadioCapabilityResponse got exception=" + ar.exception);
+                //mSetRadioAccessFamilyStatus will be set anyway to SET_RC_STATUS_FAIL
+                // if either of them fail at issueFinish() method below,i.e. both phone id count
+                // is set to SET_RC_STATUS_FAIL.
                 mTransactionFailed = true;
             } else {
+                int id = rc.getPhoneId();
                 logd("onStartRadioCapabilityResponse: phoneId=" + id + " status=STARTED");
                 mSetRadioAccessFamilyStatus[id] = SET_RC_STATUS_STARTED;
             }
@@ -481,13 +490,20 @@
      * @param msg obj field isa RadioCapability
      */
     void onFinishRadioCapabilityResponse(Message msg) {
-        RadioCapability rc = (RadioCapability) ((AsyncResult) msg.obj).result;
-        if ((rc == null) || (rc.getSession() != mRadioCapabilitySessionId)) {
-            logd("onFinishRadioCapabilityResponse: Ignore session=" + mRadioCapabilitySessionId
-                    + " rc=" + rc);
-            return;
-        }
         synchronized (mSetRadioAccessFamilyStatus) {
+            AsyncResult ar = (AsyncResult)  msg.obj;
+            RadioCapability rc = (RadioCapability) ((AsyncResult) msg.obj).result;
+            // Added exception condition on finish to continue to revert if exception occurred.
+            // Checking session validity during exception is not valid
+            if (ar.exception == null
+                    && ((rc == null) || (rc.getSession() != mRadioCapabilitySessionId))) {
+                logd("onFinishRadioCapabilityResponse: Ignore session="
+                        + mRadioCapabilitySessionId + " rc=" + rc);
+                return;
+            }
+            if (ar.exception != null) {
+                logd("onFinishRadioCapabilityResponse got exception=" + ar.exception);
+            }
             logd(" onFinishRadioCapabilityResponse mRadioAccessFamilyStatusCounter="
                     + mRadioAccessFamilyStatusCounter);
             mRadioAccessFamilyStatusCounter--;
@@ -575,7 +591,6 @@
             clearTransaction();
         } else {
             intent = new Intent(TelephonyIntents.ACTION_SET_RADIO_CAPABILITY_FAILED);
-
             // now revert.
             mTransactionFailed = false;
             RadioAccessFamily[] rafs = new RadioAccessFamily[mPhones.length];
@@ -602,6 +617,7 @@
             }
 
             if (isWakeLockHeld()) {
+                logd("clearTransaction:checking wakelock held and releasing");
                 mWakeLock.release();
             }
         }
diff --git a/src/java/com/android/internal/telephony/RIL.java b/src/java/com/android/internal/telephony/RIL.java
index 0249f47..5ecdfcb 100644
--- a/src/java/com/android/internal/telephony/RIL.java
+++ b/src/java/com/android/internal/telephony/RIL.java
@@ -16,6 +16,15 @@
 
 package com.android.internal.telephony;
 
+import static android.telephony.TelephonyManager.HAL_SERVICE_DATA;
+import static android.telephony.TelephonyManager.HAL_SERVICE_IMS;
+import static android.telephony.TelephonyManager.HAL_SERVICE_MESSAGING;
+import static android.telephony.TelephonyManager.HAL_SERVICE_MODEM;
+import static android.telephony.TelephonyManager.HAL_SERVICE_NETWORK;
+import static android.telephony.TelephonyManager.HAL_SERVICE_RADIO;
+import static android.telephony.TelephonyManager.HAL_SERVICE_SIM;
+import static android.telephony.TelephonyManager.HAL_SERVICE_VOICE;
+
 import static com.android.internal.telephony.RILConstants.*;
 
 import android.annotation.NonNull;
@@ -45,7 +54,9 @@
 import android.os.WorkSource;
 import android.provider.Settings;
 import android.sysprop.TelephonyProperties;
+import android.telephony.AccessNetworkConstants;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
+import android.telephony.BarringInfo;
 import android.telephony.CarrierRestrictionRules;
 import android.telephony.CellInfo;
 import android.telephony.CellSignalStrengthCdma;
@@ -55,6 +66,7 @@
 import android.telephony.CellSignalStrengthTdscdma;
 import android.telephony.CellSignalStrengthWcdma;
 import android.telephony.ClientRequestStats;
+import android.telephony.DomainSelectionService;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.ModemActivityInfo;
 import android.telephony.NeighboringCellInfo;
@@ -67,18 +79,24 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyHistogram;
 import android.telephony.TelephonyManager;
+import android.telephony.TelephonyManager.HalService;
 import android.telephony.TelephonyManager.PrefNetworkMode;
 import android.telephony.data.DataProfile;
 import android.telephony.data.NetworkSliceInfo;
 import android.telephony.data.TrafficDescriptor;
 import android.telephony.emergency.EmergencyNumber;
+import android.telephony.ims.RegistrationManager;
+import android.telephony.ims.feature.ConnectionFailureInfo;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.text.TextUtils;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.cdma.CdmaInformationRecords;
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
+import com.android.internal.telephony.emergency.EmergencyConstants;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
+import com.android.internal.telephony.imsphone.ImsCallInfo;
 import com.android.internal.telephony.metrics.ModemRestartStats;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.nano.TelephonyProto.SmsSession;
@@ -92,8 +110,10 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.Set;
@@ -133,6 +153,9 @@
     private final ClientWakelockTracker mClientWakelockTracker = new ClientWakelockTracker();
 
     /** @hide */
+    public static final HalVersion RADIO_HAL_VERSION_UNSUPPORTED = HalVersion.UNSUPPORTED;
+
+    /** @hide */
     public static final HalVersion RADIO_HAL_VERSION_UNKNOWN = HalVersion.UNKNOWN;
 
     /** @hide */
@@ -159,8 +182,11 @@
     /** @hide */
     public static final HalVersion RADIO_HAL_VERSION_2_0 = new HalVersion(2, 0);
 
-    // IRadio version
-    private HalVersion mRadioVersion = RADIO_HAL_VERSION_UNKNOWN;
+    /** @hide */
+    public static final HalVersion RADIO_HAL_VERSION_2_1 = new HalVersion(2, 1);
+
+    // Hal version
+    private Map<Integer, HalVersion> mHalVersion = new HashMap<>();
 
     //***** Instance Variables
 
@@ -198,15 +224,9 @@
 
     private static final String PROPERTY_IS_VONR_ENABLED = "persist.radio.is_vonr_enabled_";
 
-    static final int RADIO_SERVICE = 0;
-    static final int DATA_SERVICE = 1;
-    static final int MESSAGING_SERVICE = 2;
-    static final int MODEM_SERVICE = 3;
-    static final int NETWORK_SERVICE = 4;
-    static final int SIM_SERVICE = 5;
-    static final int VOICE_SERVICE = 6;
-    static final int MIN_SERVICE_IDX = RADIO_SERVICE;
-    static final int MAX_SERVICE_IDX = VOICE_SERVICE;
+    public static final int MIN_SERVICE_IDX = HAL_SERVICE_RADIO;
+
+    public static final int MAX_SERVICE_IDX = HAL_SERVICE_IMS;
 
     /**
      * An array of sets that records if services are disabled in the HAL for a specific phone ID
@@ -233,6 +253,8 @@
     private volatile IRadio mRadioProxy = null;
     private DataResponse mDataResponse;
     private DataIndication mDataIndication;
+    private ImsResponse mImsResponse;
+    private ImsIndication mImsIndication;
     private MessagingResponse mMessagingResponse;
     private MessagingIndication mMessagingIndication;
     private ModemResponse mModemResponse;
@@ -362,11 +384,11 @@
 
                 case EVENT_AIDL_PROXY_DEAD:
                     int aidlService = msg.arg1;
-                    AtomicLong obj = (AtomicLong) msg.obj;
-                    riljLog("handleMessage: EVENT_AIDL_PROXY_DEAD cookie = " + msg.obj
+                    long msgCookie = (long) msg.obj;
+                    riljLog("handleMessage: EVENT_AIDL_PROXY_DEAD cookie = " + msgCookie
                             + ", service = " + serviceToString(aidlService) + ", cookie = "
                             + mServiceCookies.get(aidlService));
-                    if (obj.get() == mServiceCookies.get(aidlService).get()) {
+                    if (msgCookie == mServiceCookies.get(aidlService).get()) {
                         mIsRadioProxyInitialized = false;
                         resetProxyAndRequestList(aidlService);
                     }
@@ -413,8 +435,8 @@
         public void serviceDied(long cookie) {
             // Deal with service going away
             riljLog("serviceDied");
-            mRilHandler.sendMessage(mRilHandler.obtainMessage(EVENT_RADIO_PROXY_DEAD, RADIO_SERVICE,
-                    0 /* ignored arg2 */, cookie));
+            mRilHandler.sendMessage(mRilHandler.obtainMessage(EVENT_RADIO_PROXY_DEAD,
+                    HAL_SERVICE_RADIO, 0 /* ignored arg2 */, cookie));
         }
     }
 
@@ -428,8 +450,11 @@
 
         public void linkToDeath(IBinder service) throws RemoteException {
             if (service != null) {
+                riljLog("Linked to death for service " + serviceToString(mService));
                 mBinder = service;
                 mBinder.linkToDeath(this, (int) mServiceCookies.get(mService).incrementAndGet());
+            } else {
+                riljLoge("Unable to link to death for service " + serviceToString(mService));
             }
         }
 
@@ -444,13 +469,13 @@
         public void binderDied() {
             riljLog("Service " + serviceToString(mService) + " has died.");
             mRilHandler.sendMessage(mRilHandler.obtainMessage(EVENT_AIDL_PROXY_DEAD, mService,
-                    0 /* ignored arg2 */, mServiceCookies.get(mService)));
+                    0 /* ignored arg2 */, mServiceCookies.get(mService).get()));
             unlinkToDeath();
         }
     }
 
     private synchronized void resetProxyAndRequestList(int service) {
-        if (service == RADIO_SERVICE) {
+        if (service == HAL_SERVICE_RADIO) {
             mRadioProxy = null;
         } else {
             mServiceProxies.get(service).clear();
@@ -467,7 +492,7 @@
         // Clear request list on close
         clearRequestList(RADIO_NOT_AVAILABLE, false);
 
-        if (service == RADIO_SERVICE) {
+        if (service == HAL_SERVICE_RADIO) {
             getRadioProxy(null);
         } else {
             getRadioServiceProxy(service, null);
@@ -496,13 +521,13 @@
             // Disable HIDL service
             if (mRadioProxy != null) {
                 riljLog("Disable HIDL service");
-                mDisabledRadioServices.get(RADIO_SERVICE).add(mPhoneId);
+                mDisabledRadioServices.get(HAL_SERVICE_RADIO).add(mPhoneId);
             }
 
             mMockModem.bindAllMockModemService();
 
             for (int service = MIN_SERVICE_IDX; service <= MAX_SERVICE_IDX; service++) {
-                if (service == RADIO_SERVICE) continue;
+                if (service == HAL_SERVICE_RADIO) continue;
 
                 int retryCount = 0;
                 IBinder binder;
@@ -537,14 +562,26 @@
         if ((serviceName == null) || (!serviceBound)) {
             if (serviceBound) riljLog("Unbinding to MockModemService");
 
-            if (mDisabledRadioServices.get(RADIO_SERVICE).contains(mPhoneId)) {
-                mDisabledRadioServices.get(RADIO_SERVICE).clear();
+            if (mDisabledRadioServices.get(HAL_SERVICE_RADIO).contains(mPhoneId)) {
+                mDisabledRadioServices.get(HAL_SERVICE_RADIO).clear();
             }
 
             if (mMockModem != null) {
-                mRadioVersion = RADIO_HAL_VERSION_UNKNOWN;
                 mMockModem = null;
                 for (int service = MIN_SERVICE_IDX; service <= MAX_SERVICE_IDX; service++) {
+                    if (service == HAL_SERVICE_RADIO) {
+                        if (isRadioVersion2_0()) {
+                            mHalVersion.put(service, RADIO_HAL_VERSION_2_0);
+                        } else {
+                            mHalVersion.put(service, RADIO_HAL_VERSION_UNKNOWN);
+                        }
+                    } else {
+                        if (isRadioServiceSupported(service)) {
+                            mHalVersion.put(service, RADIO_HAL_VERSION_UNKNOWN);
+                        } else {
+                            mHalVersion.put(service, RADIO_HAL_VERSION_UNSUPPORTED);
+                        }
+                    }
                     resetProxyAndRequestList(service);
                 }
             }
@@ -586,7 +623,10 @@
     /** Returns a {@link IRadio} instance or null if the service is not available. */
     @VisibleForTesting
     public synchronized IRadio getRadioProxy(Message result) {
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_2_0)) return null;
+        if (mHalVersion.containsKey(HAL_SERVICE_RADIO)
+                && mHalVersion.get(HAL_SERVICE_RADIO).greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
+            return null;
+        }
         if (!SubscriptionManager.isValidPhoneId(mPhoneId)) return null;
         if (!mIsCellularSupported) {
             if (RILJ_LOGV) riljLog("getRadioProxy: Not calling getService(): wifi-only");
@@ -603,14 +643,14 @@
         }
 
         try {
-            if (mDisabledRadioServices.get(RADIO_SERVICE).contains(mPhoneId)) {
+            if (mDisabledRadioServices.get(HAL_SERVICE_RADIO).contains(mPhoneId)) {
                 riljLoge("getRadioProxy: mRadioProxy for " + HIDL_SERVICE_NAME[mPhoneId]
                         + " is disabled");
             } else {
                 try {
                     mRadioProxy = android.hardware.radio.V1_6.IRadio.getService(
                             HIDL_SERVICE_NAME[mPhoneId], true);
-                    mRadioVersion = RADIO_HAL_VERSION_1_6;
+                    mHalVersion.put(HAL_SERVICE_RADIO, RADIO_HAL_VERSION_1_6);
                 } catch (NoSuchElementException e) {
                 }
 
@@ -618,7 +658,7 @@
                     try {
                         mRadioProxy = android.hardware.radio.V1_5.IRadio.getService(
                                 HIDL_SERVICE_NAME[mPhoneId], true);
-                        mRadioVersion = RADIO_HAL_VERSION_1_5;
+                        mHalVersion.put(HAL_SERVICE_RADIO, RADIO_HAL_VERSION_1_5);
                     } catch (NoSuchElementException e) {
                     }
                 }
@@ -627,7 +667,7 @@
                     try {
                         mRadioProxy = android.hardware.radio.V1_4.IRadio.getService(
                                 HIDL_SERVICE_NAME[mPhoneId], true);
-                        mRadioVersion = RADIO_HAL_VERSION_1_4;
+                        mHalVersion.put(HAL_SERVICE_RADIO, RADIO_HAL_VERSION_1_4);
                     } catch (NoSuchElementException e) {
                     }
                 }
@@ -636,7 +676,7 @@
                     try {
                         mRadioProxy = android.hardware.radio.V1_3.IRadio.getService(
                                 HIDL_SERVICE_NAME[mPhoneId], true);
-                        mRadioVersion = RADIO_HAL_VERSION_1_3;
+                        mHalVersion.put(HAL_SERVICE_RADIO, RADIO_HAL_VERSION_1_3);
                     } catch (NoSuchElementException e) {
                     }
                 }
@@ -645,7 +685,7 @@
                     try {
                         mRadioProxy = android.hardware.radio.V1_2.IRadio.getService(
                                 HIDL_SERVICE_NAME[mPhoneId], true);
-                        mRadioVersion = RADIO_HAL_VERSION_1_2;
+                        mHalVersion.put(HAL_SERVICE_RADIO, RADIO_HAL_VERSION_1_2);
                     } catch (NoSuchElementException e) {
                     }
                 }
@@ -654,7 +694,7 @@
                     try {
                         mRadioProxy = android.hardware.radio.V1_1.IRadio.getService(
                                 HIDL_SERVICE_NAME[mPhoneId], true);
-                        mRadioVersion = RADIO_HAL_VERSION_1_1;
+                        mHalVersion.put(HAL_SERVICE_RADIO, RADIO_HAL_VERSION_1_1);
                     } catch (NoSuchElementException e) {
                     }
                 }
@@ -663,7 +703,7 @@
                     try {
                         mRadioProxy = android.hardware.radio.V1_0.IRadio.getService(
                                 HIDL_SERVICE_NAME[mPhoneId], true);
-                        mRadioVersion = RADIO_HAL_VERSION_1_0;
+                        mHalVersion.put(HAL_SERVICE_RADIO, RADIO_HAL_VERSION_1_0);
                     } catch (NoSuchElementException e) {
                     }
                 }
@@ -672,11 +712,11 @@
                     if (!mIsRadioProxyInitialized) {
                         mIsRadioProxyInitialized = true;
                         mRadioProxy.linkToDeath(mRadioProxyDeathRecipient,
-                                mServiceCookies.get(RADIO_SERVICE).incrementAndGet());
+                                mServiceCookies.get(HAL_SERVICE_RADIO).incrementAndGet());
                         mRadioProxy.setResponseFunctions(mRadioResponse, mRadioIndication);
                     }
                 } else {
-                    mDisabledRadioServices.get(RADIO_SERVICE).add(mPhoneId);
+                    mDisabledRadioServices.get(HAL_SERVICE_RADIO).add(mPhoneId);
                     riljLoge("getRadioProxy: set mRadioProxy for "
                             + HIDL_SERVICE_NAME[mPhoneId] + " as disabled");
                 }
@@ -701,29 +741,32 @@
 
     /**
      * Returns a {@link RadioDataProxy}, {@link RadioMessagingProxy}, {@link RadioModemProxy},
-     * {@link RadioNetworkProxy}, {@link RadioSimProxy}, {@link RadioVoiceProxy}, or an empty {@link RadioServiceProxy}
-     * if the service is not available.
+     * {@link RadioNetworkProxy}, {@link RadioSimProxy}, {@link RadioVoiceProxy},
+     * {@link RadioImsProxy}, or null if the service is not available.
      */
     @NonNull
     public <T extends RadioServiceProxy> T getRadioServiceProxy(Class<T> serviceClass,
             Message result) {
         if (serviceClass == RadioDataProxy.class) {
-            return (T) getRadioServiceProxy(DATA_SERVICE, result);
+            return (T) getRadioServiceProxy(HAL_SERVICE_DATA, result);
         }
         if (serviceClass == RadioMessagingProxy.class) {
-            return (T) getRadioServiceProxy(MESSAGING_SERVICE, result);
+            return (T) getRadioServiceProxy(HAL_SERVICE_MESSAGING, result);
         }
         if (serviceClass == RadioModemProxy.class) {
-            return (T) getRadioServiceProxy(MODEM_SERVICE, result);
+            return (T) getRadioServiceProxy(HAL_SERVICE_MODEM, result);
         }
         if (serviceClass == RadioNetworkProxy.class) {
-            return (T) getRadioServiceProxy(NETWORK_SERVICE, result);
+            return (T) getRadioServiceProxy(HAL_SERVICE_NETWORK, result);
         }
         if (serviceClass == RadioSimProxy.class) {
-            return (T) getRadioServiceProxy(SIM_SERVICE, result);
+            return (T) getRadioServiceProxy(HAL_SERVICE_SIM, result);
         }
         if (serviceClass == RadioVoiceProxy.class) {
-            return (T) getRadioServiceProxy(VOICE_SERVICE, result);
+            return (T) getRadioServiceProxy(HAL_SERVICE_VOICE, result);
+        }
+        if (serviceClass == RadioImsProxy.class) {
+            return (T) getRadioServiceProxy(HAL_SERVICE_IMS, result);
         }
         riljLoge("getRadioServiceProxy: unrecognized " + serviceClass);
         return null;
@@ -731,12 +774,15 @@
 
     /**
      * Returns a {@link RadioServiceProxy}, which is empty if the service is not available.
-     * For RADIO_SERVICE, use {@link #getRadioProxy} instead, as this will always return null.
+     * For HAL_SERVICE_RADIO, use {@link #getRadioProxy} instead, as this will always return null.
      */
     @VisibleForTesting
     @NonNull
     public synchronized RadioServiceProxy getRadioServiceProxy(int service, Message result) {
         if (!SubscriptionManager.isValidPhoneId(mPhoneId)) return mServiceProxies.get(service);
+        if ((service >= HAL_SERVICE_IMS) && !isRadioServiceSupported(service)) {
+            return mServiceProxies.get(service);
+        }
         if (!mIsCellularSupported) {
             if (RILJ_LOGV) riljLog("getRadioServiceProxy: Not calling getService(): wifi-only");
             if (result != null) {
@@ -753,168 +799,190 @@
         }
 
         try {
-            if (mDisabledRadioServices.get(service).contains(mPhoneId)) {
+            if (mMockModem == null && mDisabledRadioServices.get(service).contains(mPhoneId)) {
                 riljLoge("getRadioServiceProxy: " + serviceToString(service) + " for "
                         + HIDL_SERVICE_NAME[mPhoneId] + " is disabled");
             } else {
                 IBinder binder;
                 switch (service) {
-                    case DATA_SERVICE:
+                    case HAL_SERVICE_DATA:
                         if (mMockModem == null) {
                             binder = ServiceManager.waitForDeclaredService(
                                     android.hardware.radio.data.IRadioData.DESCRIPTOR + "/"
                                             + HIDL_SERVICE_NAME[mPhoneId]);
                         } else {
-                            binder = mMockModem.getServiceBinder(DATA_SERVICE);
+                            binder = mMockModem.getServiceBinder(HAL_SERVICE_DATA);
                         }
                         if (binder != null) {
-                            mRadioVersion = RADIO_HAL_VERSION_2_0;
-                            ((RadioDataProxy) serviceProxy).setAidl(mRadioVersion,
+                            mHalVersion.put(service, ((RadioDataProxy) serviceProxy).setAidl(
+                                    mHalVersion.get(service),
                                     android.hardware.radio.data.IRadioData.Stub.asInterface(
-                                            binder));
+                                            binder)));
                         }
                         break;
-                    case MESSAGING_SERVICE:
+                    case HAL_SERVICE_MESSAGING:
                         if (mMockModem == null) {
                             binder = ServiceManager.waitForDeclaredService(
                                     android.hardware.radio.messaging.IRadioMessaging.DESCRIPTOR
                                             + "/" + HIDL_SERVICE_NAME[mPhoneId]);
                         } else {
-                            binder = mMockModem.getServiceBinder(MESSAGING_SERVICE);
+                            binder = mMockModem.getServiceBinder(HAL_SERVICE_MESSAGING);
                         }
                         if (binder != null) {
-                            mRadioVersion = RADIO_HAL_VERSION_2_0;
-                            ((RadioMessagingProxy) serviceProxy).setAidl(mRadioVersion,
+                            mHalVersion.put(service, ((RadioMessagingProxy) serviceProxy).setAidl(
+                                    mHalVersion.get(service),
                                     android.hardware.radio.messaging.IRadioMessaging.Stub
-                                            .asInterface(binder));
+                                            .asInterface(binder)));
                         }
                         break;
-                    case MODEM_SERVICE:
+                    case HAL_SERVICE_MODEM:
                         if (mMockModem == null) {
                             binder = ServiceManager.waitForDeclaredService(
                                     android.hardware.radio.modem.IRadioModem.DESCRIPTOR + "/"
                                             + HIDL_SERVICE_NAME[mPhoneId]);
                         } else {
-                            binder = mMockModem.getServiceBinder(MODEM_SERVICE);
+                            binder = mMockModem.getServiceBinder(HAL_SERVICE_MODEM);
                         }
                         if (binder != null) {
-                            mRadioVersion = RADIO_HAL_VERSION_2_0;
-                            ((RadioModemProxy) serviceProxy).setAidl(mRadioVersion,
+                            mHalVersion.put(service, ((RadioModemProxy) serviceProxy).setAidl(
+                                    mHalVersion.get(service),
                                     android.hardware.radio.modem.IRadioModem.Stub
-                                            .asInterface(binder));
+                                            .asInterface(binder)));
                         }
                         break;
-                    case NETWORK_SERVICE:
+                    case HAL_SERVICE_NETWORK:
                         if (mMockModem == null) {
                             binder = ServiceManager.waitForDeclaredService(
                                     android.hardware.radio.network.IRadioNetwork.DESCRIPTOR + "/"
                                             + HIDL_SERVICE_NAME[mPhoneId]);
                         } else {
-                            binder = mMockModem.getServiceBinder(NETWORK_SERVICE);
+                            binder = mMockModem.getServiceBinder(HAL_SERVICE_NETWORK);
                         }
                         if (binder != null) {
-                            mRadioVersion = RADIO_HAL_VERSION_2_0;
-                            ((RadioNetworkProxy) serviceProxy).setAidl(mRadioVersion,
+                            mHalVersion.put(service, ((RadioNetworkProxy) serviceProxy).setAidl(
+                                    mHalVersion.get(service),
                                     android.hardware.radio.network.IRadioNetwork.Stub
-                                            .asInterface(binder));
+                                            .asInterface(binder)));
                         }
                         break;
-                    case SIM_SERVICE:
+                    case HAL_SERVICE_SIM:
                         if (mMockModem == null) {
                             binder = ServiceManager.waitForDeclaredService(
                                     android.hardware.radio.sim.IRadioSim.DESCRIPTOR + "/"
                                             + HIDL_SERVICE_NAME[mPhoneId]);
                         } else {
-                            binder = mMockModem.getServiceBinder(SIM_SERVICE);
+                            binder = mMockModem.getServiceBinder(HAL_SERVICE_SIM);
                         }
                         if (binder != null) {
-                            mRadioVersion = RADIO_HAL_VERSION_2_0;
-                            ((RadioSimProxy) serviceProxy).setAidl(mRadioVersion,
+                            mHalVersion.put(service, ((RadioSimProxy) serviceProxy).setAidl(
+                                    mHalVersion.get(service),
                                     android.hardware.radio.sim.IRadioSim.Stub
-                                            .asInterface(binder));
+                                            .asInterface(binder)));
                         }
                         break;
-                    case VOICE_SERVICE:
+                    case HAL_SERVICE_VOICE:
                         if (mMockModem == null) {
                             binder = ServiceManager.waitForDeclaredService(
                                     android.hardware.radio.voice.IRadioVoice.DESCRIPTOR + "/"
                                             + HIDL_SERVICE_NAME[mPhoneId]);
                         } else {
-                            binder = mMockModem.getServiceBinder(VOICE_SERVICE);
+                            binder = mMockModem.getServiceBinder(HAL_SERVICE_VOICE);
                         }
                         if (binder != null) {
-                            mRadioVersion = RADIO_HAL_VERSION_2_0;
-                            ((RadioVoiceProxy) serviceProxy).setAidl(mRadioVersion,
+                            mHalVersion.put(service, ((RadioVoiceProxy) serviceProxy).setAidl(
+                                    mHalVersion.get(service),
                                     android.hardware.radio.voice.IRadioVoice.Stub
-                                            .asInterface(binder));
+                                            .asInterface(binder)));
+                        }
+                        break;
+                    case HAL_SERVICE_IMS:
+                        if (mMockModem == null) {
+                            binder = ServiceManager.waitForDeclaredService(
+                                    android.hardware.radio.ims.IRadioIms.DESCRIPTOR + "/"
+                                            + HIDL_SERVICE_NAME[mPhoneId]);
+                        } else {
+                            binder = mMockModem.getServiceBinder(HAL_SERVICE_IMS);
+                        }
+                        if (binder != null) {
+                            mHalVersion.put(service, ((RadioImsProxy) serviceProxy).setAidl(
+                                    mHalVersion.get(service),
+                                    android.hardware.radio.ims.IRadioIms.Stub
+                                            .asInterface(binder)));
                         }
                         break;
                 }
 
-                if (serviceProxy.isEmpty() && mRadioVersion.less(RADIO_HAL_VERSION_2_0)) {
+                if (serviceProxy.isEmpty()
+                        && mHalVersion.get(service).less(RADIO_HAL_VERSION_2_0)) {
                     try {
-                        mRadioVersion = RADIO_HAL_VERSION_1_6;
-                        serviceProxy.setHidl(mRadioVersion,
+                        mHalVersion.put(service, RADIO_HAL_VERSION_1_6);
+                        serviceProxy.setHidl(mHalVersion.get(service),
                                 android.hardware.radio.V1_6.IRadio.getService(
                                         HIDL_SERVICE_NAME[mPhoneId], true));
                     } catch (NoSuchElementException e) {
                     }
                 }
 
-                if (serviceProxy.isEmpty() && mRadioVersion.less(RADIO_HAL_VERSION_2_0)) {
+                if (serviceProxy.isEmpty()
+                        && mHalVersion.get(service).less(RADIO_HAL_VERSION_2_0)) {
                     try {
-                        mRadioVersion = RADIO_HAL_VERSION_1_5;
-                        serviceProxy.setHidl(mRadioVersion,
+                        mHalVersion.put(service, RADIO_HAL_VERSION_1_5);
+                        serviceProxy.setHidl(mHalVersion.get(service),
                                 android.hardware.radio.V1_5.IRadio.getService(
                                         HIDL_SERVICE_NAME[mPhoneId], true));
                     } catch (NoSuchElementException e) {
                     }
                 }
 
-                if (serviceProxy.isEmpty() && mRadioVersion.less(RADIO_HAL_VERSION_2_0)) {
+                if (serviceProxy.isEmpty()
+                        && mHalVersion.get(service).less(RADIO_HAL_VERSION_2_0)) {
                     try {
-                        mRadioVersion = RADIO_HAL_VERSION_1_4;
-                        serviceProxy.setHidl(mRadioVersion,
+                        mHalVersion.put(service, RADIO_HAL_VERSION_1_4);
+                        serviceProxy.setHidl(mHalVersion.get(service),
                                 android.hardware.radio.V1_4.IRadio.getService(
                                         HIDL_SERVICE_NAME[mPhoneId], true));
                     } catch (NoSuchElementException e) {
                     }
                 }
 
-                if (serviceProxy.isEmpty() && mRadioVersion.less(RADIO_HAL_VERSION_2_0)) {
+                if (serviceProxy.isEmpty()
+                        && mHalVersion.get(service).less(RADIO_HAL_VERSION_2_0)) {
                     try {
-                        mRadioVersion = RADIO_HAL_VERSION_1_3;
-                        serviceProxy.setHidl(mRadioVersion,
+                        mHalVersion.put(service, RADIO_HAL_VERSION_1_3);
+                        serviceProxy.setHidl(mHalVersion.get(service),
                                 android.hardware.radio.V1_3.IRadio.getService(
                                         HIDL_SERVICE_NAME[mPhoneId], true));
                     } catch (NoSuchElementException e) {
                     }
                 }
 
-                if (serviceProxy.isEmpty() && mRadioVersion.less(RADIO_HAL_VERSION_2_0)) {
+                if (serviceProxy.isEmpty()
+                        && mHalVersion.get(service).less(RADIO_HAL_VERSION_2_0)) {
                     try {
-                        mRadioVersion = RADIO_HAL_VERSION_1_2;
-                        serviceProxy.setHidl(mRadioVersion,
+                        mHalVersion.put(service, RADIO_HAL_VERSION_1_2);
+                        serviceProxy.setHidl(mHalVersion.get(service),
                                 android.hardware.radio.V1_2.IRadio.getService(
                                         HIDL_SERVICE_NAME[mPhoneId], true));
                     } catch (NoSuchElementException e) {
                     }
                 }
 
-                if (serviceProxy.isEmpty() && mRadioVersion.less(RADIO_HAL_VERSION_2_0)) {
+                if (serviceProxy.isEmpty()
+                        && mHalVersion.get(service).less(RADIO_HAL_VERSION_2_0)) {
                     try {
-                        mRadioVersion = RADIO_HAL_VERSION_1_1;
-                        serviceProxy.setHidl(mRadioVersion,
+                        mHalVersion.put(service, RADIO_HAL_VERSION_1_1);
+                        serviceProxy.setHidl(mHalVersion.get(service),
                                 android.hardware.radio.V1_1.IRadio.getService(
                                         HIDL_SERVICE_NAME[mPhoneId], true));
                     } catch (NoSuchElementException e) {
                     }
                 }
 
-                if (serviceProxy.isEmpty() && mRadioVersion.less(RADIO_HAL_VERSION_2_0)) {
+                if (serviceProxy.isEmpty()
+                        && mHalVersion.get(service).less(RADIO_HAL_VERSION_2_0)) {
                     try {
-                        mRadioVersion = RADIO_HAL_VERSION_1_0;
-                        serviceProxy.setHidl(mRadioVersion,
+                        mHalVersion.put(service, RADIO_HAL_VERSION_1_0);
+                        serviceProxy.setHidl(mHalVersion.get(service),
                                 android.hardware.radio.V1_0.IRadio.getService(
                                         HIDL_SERVICE_NAME[mPhoneId], true));
                     } catch (NoSuchElementException e) {
@@ -924,57 +992,65 @@
                 if (!serviceProxy.isEmpty()) {
                     if (serviceProxy.isAidl()) {
                         switch (service) {
-                            case DATA_SERVICE:
+                            case HAL_SERVICE_DATA:
                                 mDeathRecipients.get(service).linkToDeath(
                                         ((RadioDataProxy) serviceProxy).getAidl().asBinder());
                                 ((RadioDataProxy) serviceProxy).getAidl().setResponseFunctions(
                                         mDataResponse, mDataIndication);
                                 break;
-                            case MESSAGING_SERVICE:
+                            case HAL_SERVICE_MESSAGING:
                                 mDeathRecipients.get(service).linkToDeath(
                                         ((RadioMessagingProxy) serviceProxy).getAidl().asBinder());
                                 ((RadioMessagingProxy) serviceProxy).getAidl().setResponseFunctions(
                                         mMessagingResponse, mMessagingIndication);
                                 break;
-                            case MODEM_SERVICE:
+                            case HAL_SERVICE_MODEM:
                                 mDeathRecipients.get(service).linkToDeath(
                                         ((RadioModemProxy) serviceProxy).getAidl().asBinder());
                                 ((RadioModemProxy) serviceProxy).getAidl().setResponseFunctions(
                                         mModemResponse, mModemIndication);
                                 break;
-                            case NETWORK_SERVICE:
+                            case HAL_SERVICE_NETWORK:
                                 mDeathRecipients.get(service).linkToDeath(
                                         ((RadioNetworkProxy) serviceProxy).getAidl().asBinder());
                                 ((RadioNetworkProxy) serviceProxy).getAidl().setResponseFunctions(
                                         mNetworkResponse, mNetworkIndication);
                                 break;
-                            case SIM_SERVICE:
+                            case HAL_SERVICE_SIM:
                                 mDeathRecipients.get(service).linkToDeath(
                                         ((RadioSimProxy) serviceProxy).getAidl().asBinder());
                                 ((RadioSimProxy) serviceProxy).getAidl().setResponseFunctions(
                                         mSimResponse, mSimIndication);
                                 break;
-                            case VOICE_SERVICE:
+                            case HAL_SERVICE_VOICE:
                                 mDeathRecipients.get(service).linkToDeath(
                                         ((RadioVoiceProxy) serviceProxy).getAidl().asBinder());
                                 ((RadioVoiceProxy) serviceProxy).getAidl().setResponseFunctions(
                                         mVoiceResponse, mVoiceIndication);
                                 break;
+                            case HAL_SERVICE_IMS:
+                                mDeathRecipients.get(service).linkToDeath(
+                                        ((RadioImsProxy) serviceProxy).getAidl().asBinder());
+                                ((RadioImsProxy) serviceProxy).getAidl().setResponseFunctions(
+                                        mImsResponse, mImsIndication);
+                                break;
                         }
                     } else {
-                        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
+                        if (mHalVersion.get(service)
+                                .greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
                             throw new AssertionError("serviceProxy shouldn't be HIDL with HAL 2.0");
                         }
                         if (!mIsRadioProxyInitialized) {
                             mIsRadioProxyInitialized = true;
                             serviceProxy.getHidl().linkToDeath(mRadioProxyDeathRecipient,
-                                    mServiceCookies.get(service).incrementAndGet());
+                                    mServiceCookies.get(HAL_SERVICE_RADIO).incrementAndGet());
                             serviceProxy.getHidl().setResponseFunctions(
                                     mRadioResponse, mRadioIndication);
                         }
                     }
                 } else {
                     mDisabledRadioServices.get(service).add(mPhoneId);
+                    mHalVersion.put(service, RADIO_HAL_VERSION_UNKNOWN);
                     riljLoge("getRadioServiceProxy: set " + serviceToString(service) + " for "
                             + HIDL_SERVICE_NAME[mPhoneId] + " as disabled");
                 }
@@ -1003,7 +1079,7 @@
         for (int service = MIN_SERVICE_IDX; service <= MAX_SERVICE_IDX; service++) {
             if (active) {
                 // Try to connect to RIL services and set response functions.
-                if (service == RADIO_SERVICE) {
+                if (service == HAL_SERVICE_RADIO) {
                     getRadioProxy(null);
                 } else {
                     getRadioServiceProxy(service, null);
@@ -1044,7 +1120,11 @@
             mRadioBugDetector = new RadioBugDetector(context, mPhoneId);
         }
         try {
-            if (isRadioVersion2_0()) mRadioVersion = RADIO_HAL_VERSION_2_0;
+            if (isRadioVersion2_0()) {
+                mHalVersion.put(HAL_SERVICE_RADIO, RADIO_HAL_VERSION_2_0);
+            } else {
+                mHalVersion.put(HAL_SERVICE_RADIO, RADIO_HAL_VERSION_UNKNOWN);
+            }
         } catch (SecurityException ex) {
             /* TODO(b/211920208): instead of the following workaround (guessing if we're in a test
              * based on proxies being populated), mock ServiceManager to not throw
@@ -1060,6 +1140,8 @@
         mRadioIndication = new RadioIndication(this);
         mDataResponse = new DataResponse(this);
         mDataIndication = new DataIndication(this);
+        mImsResponse = new ImsResponse(this);
+        mImsIndication = new ImsIndication(this);
         mMessagingResponse = new MessagingResponse(this);
         mMessagingIndication = new MessagingIndication(this);
         mModemResponse = new ModemResponse(this);
@@ -1073,19 +1155,33 @@
         mRilHandler = new RilHandler();
         mRadioProxyDeathRecipient = new RadioProxyDeathRecipient();
         for (int service = MIN_SERVICE_IDX; service <= MAX_SERVICE_IDX; service++) {
-            if (service != RADIO_SERVICE) {
+            if (service != HAL_SERVICE_RADIO) {
+                try {
+                    if (isRadioServiceSupported(service)) {
+                        mHalVersion.put(service, RADIO_HAL_VERSION_UNKNOWN);
+                    } else {
+                        mHalVersion.put(service, RADIO_HAL_VERSION_UNSUPPORTED);
+                    }
+                } catch (SecurityException ex) {
+                    /* TODO(b/211920208): instead of the following workaround (guessing if
+                    * we're in a test based on proxies being populated), mock ServiceManager
+                    * to not throw SecurityException and return correct value based on what
+                    * HAL we're testing. */
+                    if (proxies == null) throw ex;
+                }
                 mDeathRecipients.put(service, new BinderServiceDeathRecipient(service));
             }
             mDisabledRadioServices.put(service, new HashSet<>());
             mServiceCookies.put(service, new AtomicLong(0));
         }
         if (proxies == null) {
-            mServiceProxies.put(DATA_SERVICE, new RadioDataProxy());
-            mServiceProxies.put(MESSAGING_SERVICE, new RadioMessagingProxy());
-            mServiceProxies.put(MODEM_SERVICE, new RadioModemProxy());
-            mServiceProxies.put(NETWORK_SERVICE, new RadioNetworkProxy());
-            mServiceProxies.put(SIM_SERVICE, new RadioSimProxy());
-            mServiceProxies.put(VOICE_SERVICE, new RadioVoiceProxy());
+            mServiceProxies.put(HAL_SERVICE_DATA, new RadioDataProxy());
+            mServiceProxies.put(HAL_SERVICE_MESSAGING, new RadioMessagingProxy());
+            mServiceProxies.put(HAL_SERVICE_MODEM, new RadioModemProxy());
+            mServiceProxies.put(HAL_SERVICE_NETWORK, new RadioNetworkProxy());
+            mServiceProxies.put(HAL_SERVICE_SIM, new RadioSimProxy());
+            mServiceProxies.put(HAL_SERVICE_VOICE, new RadioVoiceProxy());
+            mServiceProxies.put(HAL_SERVICE_IMS, new RadioImsProxy());
         } else {
             mServiceProxies = proxies;
         }
@@ -1114,7 +1210,7 @@
         // Set radio callback; needed to set RadioIndication callback (should be done after
         // wakelock stuff is initialized above as callbacks are received on separate binder threads)
         for (int service = MIN_SERVICE_IDX; service <= MAX_SERVICE_IDX; service++) {
-            if (service == RADIO_SERVICE) {
+            if (service == HAL_SERVICE_RADIO) {
                 getRadioProxy(null);
             } else {
                 if (proxies == null) {
@@ -1122,30 +1218,62 @@
                     getRadioServiceProxy(service, null);
                 }
             }
-        }
 
-        if (RILJ_LOGD) {
-            riljLog("Radio HAL version: " + mRadioVersion);
+            if (RILJ_LOGD) {
+                riljLog("HAL version of " + serviceToString(service)
+                        + ": " + mHalVersion.get(service));
+            }
         }
     }
 
     private boolean isRadioVersion2_0() {
-        final String[] serviceNames = new String[] {
-            android.hardware.radio.data.IRadioData.DESCRIPTOR,
-            android.hardware.radio.messaging.IRadioMessaging.DESCRIPTOR,
-            android.hardware.radio.modem.IRadioModem.DESCRIPTOR,
-            android.hardware.radio.network.IRadioNetwork.DESCRIPTOR,
-            android.hardware.radio.sim.IRadioSim.DESCRIPTOR,
-            android.hardware.radio.voice.IRadioVoice.DESCRIPTOR,
-        };
-        for (String serviceName : serviceNames) {
-            if (ServiceManager.isDeclared(serviceName + '/' + HIDL_SERVICE_NAME[mPhoneId])) {
+        for (int service = HAL_SERVICE_DATA; service <= MAX_SERVICE_IDX; service++) {
+            if (isRadioServiceSupported(service)) {
                 return true;
             }
         }
         return false;
     }
 
+    private boolean isRadioServiceSupported(int service) {
+        String serviceName = "";
+
+        if (service == HAL_SERVICE_RADIO) {
+            return true;
+        }
+
+        switch (service) {
+            case HAL_SERVICE_DATA:
+                serviceName = android.hardware.radio.data.IRadioData.DESCRIPTOR;
+                break;
+            case HAL_SERVICE_MESSAGING:
+                serviceName = android.hardware.radio.messaging.IRadioMessaging.DESCRIPTOR;
+                break;
+            case HAL_SERVICE_MODEM:
+                serviceName = android.hardware.radio.modem.IRadioModem.DESCRIPTOR;
+                break;
+            case HAL_SERVICE_NETWORK:
+                serviceName = android.hardware.radio.network.IRadioNetwork.DESCRIPTOR;
+                break;
+            case HAL_SERVICE_SIM:
+                serviceName = android.hardware.radio.sim.IRadioSim.DESCRIPTOR;
+                break;
+            case HAL_SERVICE_VOICE:
+                serviceName = android.hardware.radio.voice.IRadioVoice.DESCRIPTOR;
+                break;
+            case HAL_SERVICE_IMS:
+                serviceName = android.hardware.radio.ims.IRadioIms.DESCRIPTOR;
+                break;
+        }
+
+        if (!serviceName.equals("")
+                && ServiceManager.isDeclared(serviceName + '/' + HIDL_SERVICE_NAME[mPhoneId])) {
+            return true;
+        }
+
+        return false;
+    }
+
     private boolean isRadioBugDetectionEnabled() {
         return Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.ENABLE_RADIO_BUG_DETECTION, 1) != 0;
@@ -1205,7 +1333,7 @@
             try {
                 simProxy.getIccCardStatus(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "getIccCardStatus", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "getIccCardStatus", e);
             }
         }
     }
@@ -1252,7 +1380,7 @@
                 simProxy.supplyIccPinForApp(rr.mSerial, RILUtils.convertNullToEmptyString(pin),
                         RILUtils.convertNullToEmptyString(aid));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "supplyIccPinForApp", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "supplyIccPinForApp", e);
             }
         }
     }
@@ -1279,7 +1407,7 @@
                         RILUtils.convertNullToEmptyString(newPin),
                         RILUtils.convertNullToEmptyString(aid));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "supplyIccPukForApp", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "supplyIccPukForApp", e);
             }
         }
     }
@@ -1305,7 +1433,7 @@
                 simProxy.supplyIccPin2ForApp(rr.mSerial, RILUtils.convertNullToEmptyString(pin),
                         RILUtils.convertNullToEmptyString(aid));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "supplyIccPin2ForApp", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "supplyIccPin2ForApp", e);
             }
         }
     }
@@ -1332,7 +1460,7 @@
                         RILUtils.convertNullToEmptyString(newPin2),
                         RILUtils.convertNullToEmptyString(aid));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "supplyIccPuk2ForApp", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "supplyIccPuk2ForApp", e);
             }
         }
     }
@@ -1360,7 +1488,7 @@
                         RILUtils.convertNullToEmptyString(newPin),
                         RILUtils.convertNullToEmptyString(aid));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "changeIccPinForApp", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "changeIccPinForApp", e);
             }
         }
     }
@@ -1388,7 +1516,7 @@
                         RILUtils.convertNullToEmptyString(newPin2),
                         RILUtils.convertNullToEmptyString(aid));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "changeIccPin2ForApp", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "changeIccPin2ForApp", e);
             }
         }
     }
@@ -1410,7 +1538,7 @@
                         RILUtils.convertNullToEmptyString(netpin));
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(
-                        NETWORK_SERVICE, "supplyNetworkDepersonalization", e);
+                        HAL_SERVICE_NETWORK, "supplyNetworkDepersonalization", e);
             }
         }
     }
@@ -1420,7 +1548,7 @@
             Message result) {
         RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
         if (simProxy.isEmpty()) return;
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
+        if (mHalVersion.get(HAL_SERVICE_SIM).greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
             RILRequest rr = obtainRequest(RIL_REQUEST_ENTER_SIM_DEPERSONALIZATION, result,
                     mRILDefaultWorkSource);
 
@@ -1433,7 +1561,7 @@
                 simProxy.supplySimDepersonalization(rr.mSerial, persoType,
                         RILUtils.convertNullToEmptyString(controlKey));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "supplySimDepersonalization", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "supplySimDepersonalization", e);
             }
         } else {
             if (PersoSubState.PERSOSUBSTATE_SIM_NETWORK == persoType) {
@@ -1465,7 +1593,7 @@
             try {
                 voiceProxy.getCurrentCalls(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "getCurrentCalls", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getCurrentCalls", e);
             }
         }
     }
@@ -1481,7 +1609,7 @@
     public void enableModem(boolean enable, Message result) {
         RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class, result);
         if (modemProxy.isEmpty()) return;
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_3)) {
+        if (mHalVersion.get(HAL_SERVICE_MODEM).greaterOrEqual(RADIO_HAL_VERSION_1_3)) {
             RILRequest rr = obtainRequest(RIL_REQUEST_ENABLE_MODEM, result, mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
@@ -1492,7 +1620,8 @@
             try {
                 modemProxy.enableModem(rr.mSerial, enable);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MODEM_SERVICE, "enableModem", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM,
+                        "enableModem", e);
             }
         } else {
             if (RILJ_LOGV) riljLog("enableModem: not supported.");
@@ -1509,7 +1638,7 @@
             Message result) {
         RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
         if (networkProxy.isEmpty()) return;
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_3)) {
+        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_1_3)) {
             RILRequest rr = obtainRequest(RIL_REQUEST_SET_SYSTEM_SELECTION_CHANNELS, result,
                     mRILDefaultWorkSource);
 
@@ -1521,7 +1650,8 @@
             try {
                 networkProxy.setSystemSelectionChannels(rr.mSerial, specifiers);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setSystemSelectionChannels", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK,
+                        "setSystemSelectionChannels", e);
             }
         } else {
             if (RILJ_LOGV) riljLog("setSystemSelectionChannels: not supported.");
@@ -1537,7 +1667,7 @@
     public void getSystemSelectionChannels(Message result) {
         RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
         if (networkProxy.isEmpty()) return;
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
+        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
             RILRequest rr = obtainRequest(RIL_REQUEST_GET_SYSTEM_SELECTION_CHANNELS, result,
                     mRILDefaultWorkSource);
 
@@ -1549,7 +1679,8 @@
             try {
                 networkProxy.getSystemSelectionChannels(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getSystemSelectionChannels", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK,
+                        "getSystemSelectionChannels", e);
             }
         } else {
             if (RILJ_LOGV) riljLog("getSystemSelectionChannels: not supported.");
@@ -1565,7 +1696,7 @@
     public void getModemStatus(Message result) {
         RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class, result);
         if (modemProxy.isEmpty()) return;
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_3)) {
+        if (mHalVersion.get(HAL_SERVICE_MODEM).greaterOrEqual(RADIO_HAL_VERSION_1_3)) {
             RILRequest rr = obtainRequest(RIL_REQUEST_GET_MODEM_STATUS, result,
                     mRILDefaultWorkSource);
 
@@ -1576,7 +1707,7 @@
             try {
                 modemProxy.getModemStackStatus(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MODEM_SERVICE, "getModemStatus", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "getModemStatus", e);
             }
         } else {
             if (RILJ_LOGV) riljLog("getModemStatus: not supported.");
@@ -1591,7 +1722,8 @@
     @Override
     public void dial(String address, boolean isEmergencyCall, EmergencyNumber emergencyNumberInfo,
             boolean hasKnownUserIntentEmergency, int clirMode, UUSInfo uusInfo, Message result) {
-        if (isEmergencyCall && mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_4)
+        if (isEmergencyCall
+                && mHalVersion.get(HAL_SERVICE_VOICE).greaterOrEqual(RADIO_HAL_VERSION_1_4)
                 && emergencyNumberInfo != null) {
             emergencyDial(address, emergencyNumberInfo, hasKnownUserIntentEmergency, clirMode,
                     uusInfo, result);
@@ -1609,7 +1741,7 @@
             try {
                 voiceProxy.dial(rr.mSerial, address, clirMode, uusInfo);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "dial", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "dial", e);
             }
         }
     }
@@ -1618,7 +1750,7 @@
             boolean hasKnownUserIntentEmergency, int clirMode, UUSInfo uusInfo, Message result) {
         RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
         if (voiceProxy.isEmpty()) return;
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_4)) {
+        if (mHalVersion.get(HAL_SERVICE_VOICE).greaterOrEqual(RADIO_HAL_VERSION_1_4)) {
             RILRequest rr = obtainRequest(RIL_REQUEST_EMERGENCY_DIAL, result,
                     mRILDefaultWorkSource);
 
@@ -1631,7 +1763,7 @@
                 voiceProxy.emergencyDial(rr.mSerial, RILUtils.convertNullToEmptyString(address),
                         emergencyNumberInfo, hasKnownUserIntentEmergency, clirMode, uusInfo);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "emergencyDial", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "emergencyDial", e);
             }
         } else {
             riljLoge("emergencyDial is not supported with 1.4 below IRadio");
@@ -1650,13 +1782,13 @@
             RILRequest rr = obtainRequest(RIL_REQUEST_GET_IMSI, result, mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + ">  " + RILUtils.requestToString(rr.mRequest)
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                         + " aid = " + aid);
             }
             try {
                 simProxy.getImsiForApp(rr.mSerial, RILUtils.convertNullToEmptyString(aid));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "getImsiForApp", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "getImsiForApp", e);
             }
         }
     }
@@ -1675,7 +1807,7 @@
             try {
                 voiceProxy.hangup(rr.mSerial, gsmIndex);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "hangup", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "hangup", e);
             }
         }
     }
@@ -1695,7 +1827,7 @@
             try {
                 voiceProxy.hangupWaitingOrBackground(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "hangupWaitingOrBackground", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "hangupWaitingOrBackground", e);
             }
         }
     }
@@ -1716,7 +1848,7 @@
                 voiceProxy.hangupForegroundResumeBackground(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(
-                        VOICE_SERVICE, "hangupForegroundResumeBackground", e);
+                        HAL_SERVICE_VOICE, "hangupForegroundResumeBackground", e);
             }
         }
     }
@@ -1735,7 +1867,8 @@
             try {
                 voiceProxy.switchWaitingOrHoldingAndActive(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "switchWaitingOrHoldingAndActive", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE,
+                        "switchWaitingOrHoldingAndActive", e);
             }
         }
     }
@@ -1753,7 +1886,7 @@
             try {
                 voiceProxy.conference(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "conference", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "conference", e);
             }
         }
     }
@@ -1771,7 +1904,7 @@
             try {
                 voiceProxy.rejectCall(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "rejectCall", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "rejectCall", e);
             }
         }
     }
@@ -1790,7 +1923,7 @@
             try {
                 voiceProxy.getLastCallFailCause(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "getLastCallFailCause", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getLastCallFailCause", e);
             }
         }
     }
@@ -1809,7 +1942,7 @@
             try {
                 networkProxy.getSignalStrength(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getSignalStrength", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getSignalStrength", e);
             }
         }
     }
@@ -1833,7 +1966,7 @@
             try {
                 networkProxy.getVoiceRegistrationState(rr.mSerial, overrideHalVersion);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getVoiceRegistrationState", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getVoiceRegistrationState", e);
             }
         }
     }
@@ -1857,7 +1990,7 @@
             try {
                 networkProxy.getDataRegistrationState(rr.mSerial, overrideHalVersion);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getDataRegistrationState", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getDataRegistrationState", e);
             }
         }
     }
@@ -1875,7 +2008,7 @@
             try {
                 networkProxy.getOperator(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getOperator", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getOperator", e);
             }
         }
     }
@@ -1898,7 +2031,7 @@
                 modemProxy.setRadioPower(rr.mSerial, on, forEmergencyCall,
                         preferredForEmergencyCall);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MODEM_SERVICE, "setRadioPower", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "setRadioPower", e);
             }
         }
     }
@@ -1917,7 +2050,7 @@
             try {
                 voiceProxy.sendDtmf(rr.mSerial, c + "");
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "sendDtmf", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "sendDtmf", e);
             }
         }
     }
@@ -1939,7 +2072,7 @@
                 mMetrics.writeRilSendSms(mPhoneId, rr.mSerial, SmsSession.Event.Tech.SMS_GSM,
                         SmsSession.Event.Format.SMS_FORMAT_3GPP, getOutgoingSmsMessageId(result));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "sendSMS", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "sendSMS", e);
             }
         }
     }
@@ -1980,7 +2113,7 @@
                 mMetrics.writeRilSendSms(mPhoneId, rr.mSerial, SmsSession.Event.Tech.SMS_GSM,
                         SmsSession.Event.Format.SMS_FORMAT_3GPP, getOutgoingSmsMessageId(result));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "sendSMSExpectMore", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "sendSMSExpectMore", e);
             }
         }
     }
@@ -2010,8 +2143,21 @@
                 dataProxy.setupDataCall(rr.mSerial, mPhoneId, accessNetworkType, dataProfile,
                         isRoaming, allowRoaming, reason, linkProperties, pduSessionId, sliceInfo,
                         trafficDescriptor, matchAllRuleAllowed);
-            } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(DATA_SERVICE, "setupDataCall", e);
+            } catch (RemoteException e) {
+                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "setupDataCall", e);
+            } catch (RuntimeException e) {
+                riljLoge("setupDataCall RuntimeException: " + e);
+                int error = RadioError.SYSTEM_ERR;
+                int responseType = RadioResponseType.SOLICITED;
+                processResponseInternal(HAL_SERVICE_DATA, rr.mSerial, error, responseType);
+                processResponseDoneInternal(rr, error, responseType, null);
+            }
+        } else {
+            riljLoge("setupDataCall: DataProxy is empty");
+            if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(RADIO_NOT_AVAILABLE));
+                result.sendToTarget();
             }
         }
     }
@@ -2048,7 +2194,7 @@
                         RILUtils.convertNullToEmptyString(pin2),
                         RILUtils.convertNullToEmptyString(aid));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "iccIoForApp", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "iccIoForApp", e);
             }
         }
     }
@@ -2070,7 +2216,7 @@
             try {
                 voiceProxy.sendUssd(rr.mSerial, RILUtils.convertNullToEmptyString(ussd));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "sendUssd", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "sendUssd", e);
             }
         }
     }
@@ -2089,7 +2235,7 @@
             try {
                 voiceProxy.cancelPendingUssd(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "cancelPendingUssd", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "cancelPendingUssd", e);
             }
         }
     }
@@ -2107,7 +2253,7 @@
             try {
                 voiceProxy.getClir(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "getClir", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getClir", e);
             }
         }
     }
@@ -2126,7 +2272,7 @@
             try {
                 voiceProxy.setClir(rr.mSerial, clirMode);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "setClir", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "setClir", e);
             }
         }
     }
@@ -2147,7 +2293,7 @@
             try {
                 voiceProxy.getCallForwardStatus(rr.mSerial, cfReason, serviceClass, number);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "getCallForwardStatus", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getCallForwardStatus", e);
             }
         }
     }
@@ -2170,7 +2316,7 @@
                 voiceProxy.setCallForward(
                         rr.mSerial, action, cfReason, serviceClass, number, timeSeconds);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "setCallForward", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "setCallForward", e);
             }
         }
     }
@@ -2190,7 +2336,7 @@
             try {
                 voiceProxy.getCallWaiting(rr.mSerial, serviceClass);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "getCallWaiting", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getCallWaiting", e);
             }
         }
     }
@@ -2210,7 +2356,7 @@
             try {
                 voiceProxy.setCallWaiting(rr.mSerial, enable, serviceClass);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "setCallWaiting", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "setCallWaiting", e);
             }
         }
     }
@@ -2231,7 +2377,7 @@
             try {
                 messagingProxy.acknowledgeLastIncomingGsmSms(rr.mSerial, success, cause);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE,
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING,
                         "acknowledgeLastIncomingGsmSms", e);
             }
         }
@@ -2251,7 +2397,7 @@
                 voiceProxy.acceptCall(rr.mSerial);
                 mMetrics.writeRilAnswer(mPhoneId, rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "acceptCall", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "acceptCall", e);
             }
         }
     }
@@ -2273,7 +2419,7 @@
                 dataProxy.deactivateDataCall(rr.mSerial, cid, reason);
                 mMetrics.writeRilDeactivateDataCall(mPhoneId, rr.mSerial, cid, reason);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(DATA_SERVICE, "deactivateDataCall", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "deactivateDataCall", e);
             }
         }
     }
@@ -2304,7 +2450,7 @@
                         RILUtils.convertNullToEmptyString(password),
                         serviceClass, RILUtils.convertNullToEmptyString(appId));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "getFacilityLockForApp", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "getFacilityLockForApp", e);
             }
         }
     }
@@ -2335,7 +2481,7 @@
                         RILUtils.convertNullToEmptyString(password), serviceClass,
                         RILUtils.convertNullToEmptyString(appId));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "setFacilityLockForApp", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "setFacilityLockForApp", e);
             }
         }
     }
@@ -2360,7 +2506,7 @@
                         RILUtils.convertNullToEmptyString(oldPwd),
                         RILUtils.convertNullToEmptyString(newPwd));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "changeBarringPassword", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "changeBarringPassword", e);
             }
         }
     }
@@ -2379,7 +2525,7 @@
             try {
                 networkProxy.getNetworkSelectionMode(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getNetworkSelectionMode", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getNetworkSelectionMode", e);
             }
         }
     }
@@ -2399,7 +2545,7 @@
                 networkProxy.setNetworkSelectionModeAutomatic(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(
-                        NETWORK_SERVICE, "setNetworkSelectionModeAutomatic", e);
+                        HAL_SERVICE_NETWORK, "setNetworkSelectionModeAutomatic", e);
             }
         }
     }
@@ -2420,7 +2566,8 @@
                 networkProxy.setNetworkSelectionModeManual(rr.mSerial,
                         RILUtils.convertNullToEmptyString(operatorNumeric), ran);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setNetworkSelectionModeManual", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK,
+                        "setNetworkSelectionModeManual", e);
             }
         }
     }
@@ -2439,7 +2586,7 @@
             try {
                 networkProxy.getAvailableNetworks(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getAvailableNetworks", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getAvailableNetworks", e);
             }
         }
     }
@@ -2454,7 +2601,7 @@
     public void startNetworkScan(NetworkScanRequest networkScanRequest, Message result) {
         RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
         if (networkProxy.isEmpty()) return;
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_1)) {
+        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_1_1)) {
             HalVersion overrideHalVersion = getCompatVersion(RIL_REQUEST_START_NETWORK_SCAN);
             if (RILJ_LOGD) {
                 riljLog("startNetworkScan: overrideHalVersion=" + overrideHalVersion);
@@ -2471,7 +2618,7 @@
                 networkProxy.startNetworkScan(rr.mSerial, networkScanRequest, overrideHalVersion,
                         result);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "startNetworkScan", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "startNetworkScan", e);
             }
         } else {
             if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "startNetworkScan: REQUEST_NOT_SUPPORTED");
@@ -2487,7 +2634,7 @@
     public void stopNetworkScan(Message result) {
         RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
         if (networkProxy.isEmpty()) return;
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_1)) {
+        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_1_1)) {
             RILRequest rr = obtainRequest(RIL_REQUEST_STOP_NETWORK_SCAN, result,
                     mRILDefaultWorkSource);
 
@@ -2498,7 +2645,7 @@
             try {
                 networkProxy.stopNetworkScan(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "stopNetworkScan", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "stopNetworkScan", e);
             }
         } else {
             if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "stopNetworkScan: REQUEST_NOT_SUPPORTED");
@@ -2524,7 +2671,7 @@
             try {
                 voiceProxy.startDtmf(rr.mSerial, c + "");
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "startDtmf", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "startDtmf", e);
             }
         }
     }
@@ -2542,7 +2689,7 @@
             try {
                 voiceProxy.stopDtmf(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "stopDtmf", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "stopDtmf", e);
             }
         }
     }
@@ -2562,7 +2709,7 @@
             try {
                 voiceProxy.separateConnection(rr.mSerial, gsmIndex);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "separateConnection", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "separateConnection", e);
             }
         }
     }
@@ -2581,7 +2728,7 @@
             try {
                 modemProxy.getBasebandVersion(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MODEM_SERVICE, "getBasebandVersion", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "getBasebandVersion", e);
             }
         }
     }
@@ -2600,7 +2747,7 @@
             try {
                 voiceProxy.setMute(rr.mSerial, enableMute);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "setMute", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "setMute", e);
             }
         }
     }
@@ -2618,7 +2765,7 @@
             try {
                 voiceProxy.getMute(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "getMute", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getMute", e);
             }
         }
     }
@@ -2636,7 +2783,7 @@
             try {
                 voiceProxy.getClip(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "getClip", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getClip", e);
             }
         }
     }
@@ -2664,7 +2811,7 @@
             try {
                 dataProxy.getDataCallList(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(DATA_SERVICE, "getDataCallList", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "getDataCallList", e);
             }
         }
     }
@@ -2695,7 +2842,8 @@
             try {
                 networkProxy.setSuppServiceNotifications(rr.mSerial, enable);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setSuppServiceNotifications", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK,
+                        "setSuppServiceNotifications", e);
             }
         }
     }
@@ -2718,7 +2866,7 @@
                         RILUtils.convertNullToEmptyString(smsc),
                         RILUtils.convertNullToEmptyString(pdu));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "writeSmsToSim", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "writeSmsToSim", e);
             }
         }
     }
@@ -2739,7 +2887,7 @@
             try {
                 messagingProxy.deleteSmsOnSim(rr.mSerial, index);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "deleteSmsOnSim", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "deleteSmsOnSim", e);
             }
         }
     }
@@ -2758,7 +2906,7 @@
             try {
                 networkProxy.setBandMode(rr.mSerial, bandMode);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setBandMode", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setBandMode", e);
             }
         }
     }
@@ -2777,7 +2925,7 @@
             try {
                 networkProxy.getAvailableBandModes(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "queryAvailableBandMode", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "queryAvailableBandMode", e);
             }
         }
     }
@@ -2797,7 +2945,7 @@
             try {
                 simProxy.sendEnvelope(rr.mSerial, RILUtils.convertNullToEmptyString(contents));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "sendEnvelope", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "sendEnvelope", e);
             }
         }
     }
@@ -2819,7 +2967,7 @@
                 simProxy.sendTerminalResponseToSim(rr.mSerial,
                         RILUtils.convertNullToEmptyString(contents));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "sendTerminalResponse", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "sendTerminalResponse", e);
             }
         }
     }
@@ -2840,7 +2988,7 @@
                 simProxy.sendEnvelopeWithStatus(rr.mSerial,
                         RILUtils.convertNullToEmptyString(contents));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "sendEnvelopeWithStatus", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "sendEnvelopeWithStatus", e);
             }
         }
     }
@@ -2859,7 +3007,7 @@
             try {
                 voiceProxy.explicitCallTransfer(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "explicitCallTransfer", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "explicitCallTransfer", e);
             }
         }
     }
@@ -2881,7 +3029,7 @@
             try {
                 networkProxy.setPreferredNetworkTypeBitmap(rr.mSerial, mAllowedNetworkTypesBitmask);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setPreferredNetworkType", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setPreferredNetworkType", e);
             }
         }
     }
@@ -2900,7 +3048,7 @@
             try {
                 networkProxy.getAllowedNetworkTypesBitmap(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getPreferredNetworkType", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getPreferredNetworkType", e);
             }
         }
     }
@@ -2910,7 +3058,7 @@
             @TelephonyManager.NetworkTypeBitMask int networkTypeBitmask, Message result) {
         RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
         if (!networkProxy.isEmpty()) {
-            if (mRadioVersion.less(RADIO_HAL_VERSION_1_6)) {
+            if (mHalVersion.get(HAL_SERVICE_NETWORK).less(RADIO_HAL_VERSION_1_6)) {
                 // For older HAL, redirects the call to setPreferredNetworkType.
                 setPreferredNetworkType(
                         RadioAccessFamily.getNetworkTypeFromRaf(networkTypeBitmask), result);
@@ -2927,7 +3075,8 @@
             try {
                 networkProxy.setAllowedNetworkTypesBitmap(rr.mSerial, mAllowedNetworkTypesBitmask);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setAllowedNetworkTypeBitmask", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK,
+                        "setAllowedNetworkTypeBitmask", e);
             }
         }
     }
@@ -2946,7 +3095,8 @@
             try {
                 networkProxy.getAllowedNetworkTypesBitmap(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getAllowedNetworkTypeBitmask", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK,
+                        "getAllowedNetworkTypeBitmask", e);
             }
         }
     }
@@ -2966,7 +3116,7 @@
             try {
                 networkProxy.setLocationUpdates(rr.mSerial, enable);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setLocationUpdates", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setLocationUpdates", e);
             }
         }
     }
@@ -2978,7 +3128,7 @@
     public void isNrDualConnectivityEnabled(Message result, WorkSource workSource) {
         RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
         if (networkProxy.isEmpty()) return;
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
+        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
             RILRequest rr = obtainRequest(RIL_REQUEST_IS_NR_DUAL_CONNECTIVITY_ENABLED, result,
                     getDefaultWorkSourceIfInvalid(workSource));
 
@@ -2989,7 +3139,8 @@
             try {
                 networkProxy.isNrDualConnectivityEnabled(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "isNrDualConnectivityEnabled", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK,
+                        "isNrDualConnectivityEnabled", e);
             }
         } else {
             if (RILJ_LOGD) {
@@ -3019,7 +3170,7 @@
             WorkSource workSource) {
         RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
         if (networkProxy.isEmpty()) return;
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
+        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
             RILRequest rr = obtainRequest(RIL_REQUEST_ENABLE_NR_DUAL_CONNECTIVITY, result,
                     getDefaultWorkSourceIfInvalid(workSource));
 
@@ -3031,7 +3182,7 @@
             try {
                 networkProxy.setNrDualConnectivityState(rr.mSerial, (byte) nrDualConnectivityState);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "enableNrDualConnectivity", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "enableNrDualConnectivity", e);
             }
         } else {
             if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "enableNrDualConnectivity: REQUEST_NOT_SUPPORTED");
@@ -3057,7 +3208,7 @@
     @Override
     public void isVoNrEnabled(Message result, WorkSource workSource) {
 
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
+        if (mHalVersion.get(HAL_SERVICE_VOICE).greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
             RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
             if (!voiceProxy.isEmpty()) {
                 RILRequest rr = obtainRequest(RIL_REQUEST_IS_VONR_ENABLED , result,
@@ -3070,7 +3221,7 @@
                 try {
                     voiceProxy.isVoNrEnabled(rr.mSerial);
                 } catch (RemoteException | RuntimeException e) {
-                    handleRadioProxyExceptionForRR(VOICE_SERVICE, "isVoNrEnabled", e);
+                    handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "isVoNrEnabled", e);
                 }
             }
         } else {
@@ -3090,7 +3241,7 @@
     public void setVoNrEnabled(boolean enabled, Message result, WorkSource workSource) {
         setVoNrEnabled(enabled);
 
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
+        if (mHalVersion.get(HAL_SERVICE_VOICE).greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
             RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class, result);
             if (!voiceProxy.isEmpty()) {
                 RILRequest rr = obtainRequest(RIL_REQUEST_ENABLE_VONR, result,
@@ -3103,7 +3254,7 @@
                 try {
                     voiceProxy.setVoNrEnabled(rr.mSerial, enabled);
                 } catch (RemoteException | RuntimeException e) {
-                    handleRadioProxyExceptionForRR(VOICE_SERVICE, "setVoNrEnabled", e);
+                    handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "setVoNrEnabled", e);
                 }
             }
         } else {
@@ -3135,7 +3286,7 @@
             try {
                 simProxy.setCdmaSubscriptionSource(rr.mSerial, cdmaSubscription);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "setCdmaSubscriptionSource", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "setCdmaSubscriptionSource", e);
             }
         }
     }
@@ -3154,7 +3305,8 @@
             try {
                 networkProxy.getCdmaRoamingPreference(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "queryCdmaRoamingPreference", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK,
+                        "queryCdmaRoamingPreference", e);
             }
         }
     }
@@ -3174,7 +3326,7 @@
             try {
                 networkProxy.setCdmaRoamingPreference(rr.mSerial, cdmaRoamingType);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setCdmaRoamingPreference", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setCdmaRoamingPreference", e);
             }
         }
     }
@@ -3193,7 +3345,7 @@
             try {
                 voiceProxy.getTtyMode(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "getTtyMode", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getTtyMode", e);
             }
         }
     }
@@ -3212,7 +3364,7 @@
             try {
                 voiceProxy.setTtyMode(rr.mSerial, ttyMode);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "setTtyMode", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "setTtyMode", e);
             }
         }
     }
@@ -3232,7 +3384,7 @@
             try {
                 voiceProxy.setPreferredVoicePrivacy(rr.mSerial, enable);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "setPreferredVoicePrivacy", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "setPreferredVoicePrivacy", e);
             }
         }
     }
@@ -3251,7 +3403,7 @@
             try {
                 voiceProxy.getPreferredVoicePrivacy(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "getPreferredVoicePrivacy", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getPreferredVoicePrivacy", e);
             }
         }
     }
@@ -3271,7 +3423,7 @@
                 voiceProxy.sendCdmaFeatureCode(rr.mSerial,
                         RILUtils.convertNullToEmptyString(featureCode));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "sendCdmaFeatureCode", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "sendCdmaFeatureCode", e);
             }
         }
     }
@@ -3292,7 +3444,7 @@
                 voiceProxy.sendBurstDtmf(rr.mSerial, RILUtils.convertNullToEmptyString(dtmfString),
                         on, off);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "sendBurstDtmf", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "sendBurstDtmf", e);
             }
         }
     }
@@ -3312,13 +3464,13 @@
 
             try {
                 messagingProxy.sendCdmaSmsExpectMore(rr.mSerial, pdu);
-                if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
+                if (mHalVersion.get(HAL_SERVICE_MESSAGING).greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
                     mMetrics.writeRilSendSms(mPhoneId, rr.mSerial, SmsSession.Event.Tech.SMS_CDMA,
                             SmsSession.Event.Format.SMS_FORMAT_3GPP2,
                             getOutgoingSmsMessageId(result));
                 }
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "sendCdmaSMSExpectMore", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "sendCdmaSMSExpectMore", e);
             }
         }
     }
@@ -3340,7 +3492,7 @@
                 mMetrics.writeRilSendSms(mPhoneId, rr.mSerial, SmsSession.Event.Tech.SMS_CDMA,
                         SmsSession.Event.Format.SMS_FORMAT_3GPP2, getOutgoingSmsMessageId(result));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "sendCdmaSms", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "sendCdmaSms", e);
             }
         }
     }
@@ -3361,7 +3513,7 @@
             try {
                 messagingProxy.acknowledgeLastIncomingCdmaSms(rr.mSerial, success, cause);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE,
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING,
                         "acknowledgeLastIncomingCdmaSms", e);
             }
         }
@@ -3382,7 +3534,7 @@
             try {
                 messagingProxy.getGsmBroadcastConfig(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "getGsmBroadcastConfig", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "getGsmBroadcastConfig", e);
             }
         }
     }
@@ -3406,7 +3558,7 @@
             try {
                 messagingProxy.setGsmBroadcastConfig(rr.mSerial, config);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "setGsmBroadcastConfig", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "setGsmBroadcastConfig", e);
             }
         }
     }
@@ -3427,7 +3579,8 @@
             try {
                 messagingProxy.setGsmBroadcastActivation(rr.mSerial, activate);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "setGsmBroadcastActivation", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING,
+                        "setGsmBroadcastActivation", e);
             }
         }
     }
@@ -3447,7 +3600,7 @@
             try {
                 messagingProxy.getCdmaBroadcastConfig(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "getCdmaBroadcastConfig", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "getCdmaBroadcastConfig", e);
             }
         }
     }
@@ -3471,7 +3624,7 @@
             try {
                 messagingProxy.setCdmaBroadcastConfig(rr.mSerial, configs);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "setCdmaBroadcastConfig", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "setCdmaBroadcastConfig", e);
             }
         }
     }
@@ -3492,7 +3645,8 @@
             try {
                 messagingProxy.setCdmaBroadcastActivation(rr.mSerial, activate);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "setCdmaBroadcastActivation", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING,
+                        "setCdmaBroadcastActivation", e);
             }
         }
     }
@@ -3511,7 +3665,7 @@
             try {
                 simProxy.getCdmaSubscription(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "getCdmaSubscription", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "getCdmaSubscription", e);
             }
         }
     }
@@ -3532,7 +3686,7 @@
             try {
                 messagingProxy.writeSmsToRuim(rr.mSerial, status, pdu);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "writeSmsToRuim", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "writeSmsToRuim", e);
             }
         }
     }
@@ -3553,7 +3707,7 @@
             try {
                 messagingProxy.deleteSmsOnRuim(rr.mSerial, index);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "deleteSmsOnRuim", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "deleteSmsOnRuim", e);
             }
         }
     }
@@ -3572,7 +3726,41 @@
             try {
                 modemProxy.getDeviceIdentity(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MODEM_SERVICE, "getDeviceIdentity", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "getDeviceIdentity", e);
+            }
+        }
+    }
+
+    @Override
+    public void getImei(Message result) {
+        RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class, result);
+        if (modemProxy.isEmpty()) {
+            if (RILJ_LOGD) {
+                Rlog.e(RILJ_LOG_TAG, "getImei: modemProxy is Empty");
+            }
+            return;
+        }
+        if (mHalVersion.get(HAL_SERVICE_MODEM).greaterOrEqual(RADIO_HAL_VERSION_2_1)) {
+            RILRequest rr = obtainRequest(RIL_REQUEST_DEVICE_IMEI, result,
+                    mRILDefaultWorkSource);
+
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
+
+            try {
+                modemProxy.getImei(rr.mSerial);
+            } catch (RemoteException | RuntimeException e) {
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "getImei", e);
+            }
+        }  else {
+            if (RILJ_LOGD) {
+                Rlog.e(RILJ_LOG_TAG, "getImei: REQUEST_NOT_SUPPORTED");
+            }
+            if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
             }
         }
     }
@@ -3591,7 +3779,7 @@
             try {
                 voiceProxy.exitEmergencyCallbackMode(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "exitEmergencyCallbackMode", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "exitEmergencyCallbackMode", e);
             }
         }
     }
@@ -3611,7 +3799,7 @@
             try {
                 messagingProxy.getSmscAddress(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "getSmscAddress", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "getSmscAddress", e);
             }
         }
     }
@@ -3633,7 +3821,7 @@
                 messagingProxy.setSmscAddress(rr.mSerial,
                         RILUtils.convertNullToEmptyString(address));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "setSmscAddress", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "setSmscAddress", e);
             }
         }
     }
@@ -3654,7 +3842,7 @@
             try {
                 messagingProxy.reportSmsMemoryStatus(rr.mSerial, available);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "reportSmsMemoryStatus", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "reportSmsMemoryStatus", e);
             }
         }
     }
@@ -3673,7 +3861,7 @@
             try {
                 simProxy.reportStkServiceIsRunning(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "reportStkServiceIsRunning", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "reportStkServiceIsRunning", e);
             }
         }
     }
@@ -3692,7 +3880,7 @@
             try {
                 simProxy.getCdmaSubscriptionSource(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "getCdmaSubscriptionSource", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "getCdmaSubscriptionSource", e);
             }
         }
     }
@@ -3714,7 +3902,7 @@
                 messagingProxy.acknowledgeIncomingGsmSmsWithPdu(rr.mSerial, success,
                         RILUtils.convertNullToEmptyString(ackPdu));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE,
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING,
                         "acknowledgeIncomingGsmSmsWithPdu", e);
             }
         }
@@ -3734,7 +3922,7 @@
             try {
                 networkProxy.getVoiceRadioTechnology(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getVoiceRadioTechnology", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getVoiceRadioTechnology", e);
             }
         }
     }
@@ -3753,7 +3941,7 @@
             try {
                 networkProxy.getCellInfoList(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getCellInfoList", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getCellInfoList", e);
             }
         }
     }
@@ -3773,7 +3961,7 @@
             try {
                 networkProxy.setCellInfoListRate(rr.mSerial, rateInMillis);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setCellInfoListRate", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setCellInfoListRate", e);
             }
         }
     }
@@ -3793,7 +3981,7 @@
             try {
                 dataProxy.setInitialAttachApn(rr.mSerial, dataProfile, isRoaming);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(DATA_SERVICE, "setInitialAttachApn", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "setInitialAttachApn", e);
             }
         }
     }
@@ -3812,7 +4000,7 @@
             try {
                 networkProxy.getImsRegistrationState(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getImsRegistrationState", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getImsRegistrationState", e);
             }
         }
     }
@@ -3835,7 +4023,7 @@
                 mMetrics.writeRilSendSms(mPhoneId, rr.mSerial, SmsSession.Event.Tech.SMS_IMS,
                         SmsSession.Event.Format.SMS_FORMAT_3GPP, getOutgoingSmsMessageId(result));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "sendImsGsmSms", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "sendImsGsmSms", e);
             }
         }
     }
@@ -3857,7 +4045,7 @@
                 mMetrics.writeRilSendSms(mPhoneId, rr.mSerial, SmsSession.Event.Tech.SMS_IMS,
                         SmsSession.Event.Format.SMS_FORMAT_3GPP2, getOutgoingSmsMessageId(result));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "sendImsCdmaSms", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "sendImsCdmaSms", e);
             }
         }
     }
@@ -3885,7 +4073,7 @@
                 simProxy.iccTransmitApduBasicChannel(
                         rr.mSerial, cla, instruction, p1, p2, p3, data);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "iccTransmitApduBasicChannel", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "iccTransmitApduBasicChannel", e);
             }
         }
     }
@@ -3910,13 +4098,13 @@
                 simProxy.iccOpenLogicalChannel(rr.mSerial, RILUtils.convertNullToEmptyString(aid),
                         p2);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "iccOpenLogicalChannel", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "iccOpenLogicalChannel", e);
             }
         }
     }
 
     @Override
-    public void iccCloseLogicalChannel(int channel, Message result) {
+    public void iccCloseLogicalChannel(int channel, boolean isEs10, Message result) {
         RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
         if (!simProxy.isEmpty()) {
             RILRequest rr = obtainRequest(RIL_REQUEST_SIM_CLOSE_CHANNEL, result,
@@ -3924,20 +4112,19 @@
 
             if (RILJ_LOGD) {
                 riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
-                        + " channel = " + channel);
+                        + " channel = " + channel + " isEs10 = " + isEs10);
             }
-
             try {
-                simProxy.iccCloseLogicalChannel(rr.mSerial, channel);
+                simProxy.iccCloseLogicalChannel(rr.mSerial, channel, isEs10);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "iccCloseLogicalChannel", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "iccCloseLogicalChannel", e);
             }
         }
     }
 
     @Override
     public void iccTransmitApduLogicalChannel(int channel, int cla, int instruction, int p1, int p2,
-            int p3, String data, Message result) {
+            int p3, String data, boolean isEs10Command, Message result) {
         if (channel <= 0) {
             throw new RuntimeException(
                     "Invalid channel in iccTransmitApduLogicalChannel: " + channel);
@@ -3954,6 +4141,7 @@
                             + String.format(" channel = %d", channel)
                             + String.format(" cla = 0x%02X ins = 0x%02X", cla, instruction)
                             + String.format(" p1 = 0x%02X p2 = 0x%02X p3 = 0x%02X", p1, p2, p3)
+                            + " isEs10Command = " + isEs10Command
                             + " data = " + data);
                 } else {
                     riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
@@ -3962,9 +4150,9 @@
 
             try {
                 simProxy.iccTransmitApduLogicalChannel(
-                        rr.mSerial, channel, cla, instruction, p1, p2, p3, data);
+                        rr.mSerial, channel, cla, instruction, p1, p2, p3, data, isEs10Command);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "iccTransmitApduLogicalChannel", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "iccTransmitApduLogicalChannel", e);
             }
         }
     }
@@ -3984,7 +4172,7 @@
             try {
                 modemProxy.nvReadItem(rr.mSerial, itemID);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MODEM_SERVICE, "nvReadItem", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "nvReadItem", e);
             }
         }
     }
@@ -4005,7 +4193,7 @@
                 modemProxy.nvWriteItem(rr.mSerial, itemId,
                         RILUtils.convertNullToEmptyString(itemValue));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MODEM_SERVICE, "nvWriteItem", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "nvWriteItem", e);
             }
         }
     }
@@ -4026,7 +4214,7 @@
             try {
                 modemProxy.nvWriteCdmaPrl(rr.mSerial, preferredRoamingList);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MODEM_SERVICE, "nvWriteCdmaPrl", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "nvWriteCdmaPrl", e);
             }
         }
     }
@@ -4046,7 +4234,7 @@
             try {
                 modemProxy.nvResetConfig(rr.mSerial, resetType);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MODEM_SERVICE, "nvResetConfig", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "nvResetConfig", e);
             }
         }
     }
@@ -4068,7 +4256,7 @@
             try {
                 simProxy.setUiccSubscription(rr.mSerial, slotId, appIndex, subId, subStatus);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "setUiccSubscription", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "setUiccSubscription", e);
             }
         }
     }
@@ -4083,7 +4271,7 @@
         // EID should be supported as long as HAL >= 1.2.
         //  - in HAL 1.2 we have EID through ATR
         //  - in later HAL versions we also have EID through slot / card status.
-        return mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_2);
+        return mHalVersion.get(HAL_SERVICE_RADIO).greaterOrEqual(RADIO_HAL_VERSION_1_2);
     }
 
     @Override
@@ -4100,7 +4288,7 @@
             try {
                 dataProxy.setDataAllowed(rr.mSerial, allowed);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(DATA_SERVICE, "setDataAllowed", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "setDataAllowed", e);
             }
         }
     }
@@ -4120,7 +4308,7 @@
             try {
                 modemProxy.getHardwareConfig(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MODEM_SERVICE, "getHardwareConfig", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "getHardwareConfig", e);
             }
         }
     }
@@ -4143,7 +4331,7 @@
                         RILUtils.convertNullToEmptyString(data),
                         RILUtils.convertNullToEmptyString(aid));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "requestIccSimAuthentication", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "requestIccSimAuthentication", e);
             }
         }
     }
@@ -4166,7 +4354,7 @@
             try {
                 dataProxy.setDataProfile(rr.mSerial, dps, isRoaming);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(DATA_SERVICE, "setDataProfile", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "setDataProfile", e);
             }
         }
     }
@@ -4184,7 +4372,7 @@
             try {
                 modemProxy.requestShutdown(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MODEM_SERVICE, "requestShutdown", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "requestShutdown", e);
             }
         }
     }
@@ -4203,7 +4391,7 @@
             try {
                 modemProxy.getRadioCapability(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MODEM_SERVICE, "getRadioCapability", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "getRadioCapability", e);
             }
         }
     }
@@ -4223,14 +4411,14 @@
             try {
                 modemProxy.setRadioCapability(rr.mSerial, rc);
             } catch (Exception e) {
-                handleRadioProxyExceptionForRR(MODEM_SERVICE, "setRadioCapability", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "setRadioCapability", e);
             }
         }
     }
 
     @Override
     public void startLceService(int reportIntervalMs, boolean pullMode, Message result) {
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_2)) {
+        if (mHalVersion.get(HAL_SERVICE_RADIO).greaterOrEqual(RADIO_HAL_VERSION_1_2)) {
             // We have a 1.2 or later radio, so the LCE 1.0 LCE service control path is unused.
             // Instead the LCE functionality is always-on and provides unsolicited indications.
             if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "startLceService: REQUEST_NOT_SUPPORTED");
@@ -4254,14 +4442,14 @@
             try {
                 radioProxy.startLceService(rr.mSerial, reportIntervalMs, pullMode);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(RADIO_SERVICE, "startLceService", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_RADIO, "startLceService", e);
             }
         }
     }
 
     @Override
     public void stopLceService(Message result) {
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_2)) {
+        if (mHalVersion.get(HAL_SERVICE_RADIO).greaterOrEqual(RADIO_HAL_VERSION_1_2)) {
             // We have a 1.2 or later radio, so the LCE 1.0 LCE service control is unused.
             // Instead the LCE functionality is always-on and provides unsolicited indications.
             if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "stopLceService: REQUEST_NOT_SUPPORTED");
@@ -4284,7 +4472,7 @@
             try {
                 radioProxy.stopLceService(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(RADIO_SERVICE, "stopLceService", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_RADIO, "stopLceService", e);
             }
         }
     }
@@ -4303,7 +4491,7 @@
             long completionWindowMillis) {
         RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result);
         if (dataProxy.isEmpty()) return;
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
+        if (mHalVersion.get(HAL_SERVICE_DATA).greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
             RILRequest rr = obtainRequest(RIL_REQUEST_SET_DATA_THROTTLING, result,
                     getDefaultWorkSourceIfInvalid(workSource));
 
@@ -4318,7 +4506,7 @@
                 dataProxy.setDataThrottling(rr.mSerial, (byte) dataThrottlingAction,
                         completionWindowMillis);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(DATA_SERVICE, "setDataThrottling", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "setDataThrottling", e);
             }
         } else {
             if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "setDataThrottling: REQUEST_NOT_SUPPORTED");
@@ -4343,7 +4531,7 @@
     @Deprecated
     @Override
     public void pullLceData(Message result) {
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_2)) {
+        if (mHalVersion.get(HAL_SERVICE_RADIO).greaterOrEqual(RADIO_HAL_VERSION_1_2)) {
             // We have a 1.2 or later radio, so the LCE 1.0 LCE service control path is unused.
             // Instead the LCE functionality is always-on and provides unsolicited indications.
             if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "pullLceData: REQUEST_NOT_SUPPORTED");
@@ -4366,7 +4554,7 @@
             try {
                 radioProxy.pullLceData(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(RADIO_SERVICE, "pullLceData", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_RADIO, "pullLceData", e);
             }
         }
     }
@@ -4388,7 +4576,7 @@
                         mRilHandler.obtainMessage(EVENT_BLOCKING_RESPONSE_TIMEOUT, rr.mSerial);
                 mRilHandler.sendMessageDelayed(msg, DEFAULT_BLOCKING_MESSAGE_RESPONSE_TIMEOUT_MS);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MODEM_SERVICE, "getModemActivityInfo", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "getModemActivityInfo", e);
             }
         }
     }
@@ -4411,7 +4599,7 @@
             try {
                 simProxy.setAllowedCarriers(rr.mSerial, carrierRestrictionRules, result);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "setAllowedCarriers", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "setAllowedCarriers", e);
             }
         }
     }
@@ -4430,7 +4618,7 @@
             try {
                 simProxy.getAllowedCarriers(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "getAllowedCarriers", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "getAllowedCarriers", e);
             }
         }
     }
@@ -4450,7 +4638,7 @@
             try {
                 modemProxy.sendDeviceState(rr.mSerial, stateType, state);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MODEM_SERVICE, "sendDeviceState", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "sendDeviceState", e);
             }
         }
     }
@@ -4470,7 +4658,7 @@
             try {
                 networkProxy.setIndicationFilter(rr.mSerial, filter);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setIndicationFilter", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setIndicationFilter", e);
             }
         }
     }
@@ -4480,7 +4668,7 @@
             @NonNull List<SignalThresholdInfo> signalThresholdInfos, @Nullable Message result) {
         RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
         if (networkProxy.isEmpty()) return;
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_2)) {
+        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_1_2)) {
             RILRequest rr = obtainRequest(RIL_REQUEST_SET_SIGNAL_STRENGTH_REPORTING_CRITERIA,
                     result, mRILDefaultWorkSource);
 
@@ -4491,7 +4679,7 @@
             try {
                 networkProxy.setSignalStrengthReportingCriteria(rr.mSerial, signalThresholdInfos);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE,
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK,
                         "setSignalStrengthReportingCriteria", e);
             }
         } else {
@@ -4505,7 +4693,7 @@
             Message result) {
         RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
         if (networkProxy.isEmpty()) return;
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_2)) {
+        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_1_2)) {
             RILRequest rr = obtainRequest(RIL_REQUEST_SET_LINK_CAPACITY_REPORTING_CRITERIA, result,
                     mRILDefaultWorkSource);
 
@@ -4519,7 +4707,7 @@
                         ran);
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(
-                        NETWORK_SERVICE, "setLinkCapacityReportingCriteria", e);
+                        HAL_SERVICE_NETWORK, "setLinkCapacityReportingCriteria", e);
             }
         } else {
             riljLoge("setLinkCapacityReportingCriteria ignored on IRadio version less than 1.2");
@@ -4541,7 +4729,7 @@
             try {
                 simProxy.setSimCardPower(rr.mSerial, state, result);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "setSimCardPower", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "setSimCardPower", e);
             }
         }
     }
@@ -4552,7 +4740,7 @@
         Objects.requireNonNull(imsiEncryptionInfo, "ImsiEncryptionInfo cannot be null.");
         RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
         if (simProxy.isEmpty()) return;
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_1)) {
+        if (mHalVersion.get(HAL_SERVICE_SIM).greaterOrEqual(RADIO_HAL_VERSION_1_1)) {
             RILRequest rr = obtainRequest(RIL_REQUEST_SET_CARRIER_INFO_IMSI_ENCRYPTION, result,
                     mRILDefaultWorkSource);
             if (RILJ_LOGD) {
@@ -4562,7 +4750,8 @@
             try {
                 simProxy.setCarrierInfoForImsiEncryption(rr.mSerial, imsiEncryptionInfo);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "setCarrierInfoForImsiEncryption", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM,
+                        "setCarrierInfoForImsiEncryption", e);
             }
         } else {
             if (RILJ_LOGD) {
@@ -4582,7 +4771,7 @@
         Objects.requireNonNull(packetData, "KeepaliveRequest cannot be null.");
         RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result);
         if (dataProxy.isEmpty()) return;
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_1)) {
+        if (mHalVersion.get(HAL_SERVICE_DATA).greaterOrEqual(RADIO_HAL_VERSION_1_1)) {
             RILRequest rr = obtainRequest(RIL_REQUEST_START_KEEPALIVE, result,
                     mRILDefaultWorkSource);
 
@@ -4593,7 +4782,7 @@
             try {
                 dataProxy.startKeepalive(rr.mSerial, contextId, packetData, intervalMillis, result);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(DATA_SERVICE, "startNattKeepalive", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "startNattKeepalive", e);
             }
         } else {
             if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "startNattKeepalive: REQUEST_NOT_SUPPORTED");
@@ -4609,7 +4798,7 @@
     public void stopNattKeepalive(int sessionHandle, Message result) {
         RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result);
         if (dataProxy.isEmpty()) return;
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_1)) {
+        if (mHalVersion.get(HAL_SERVICE_DATA).greaterOrEqual(RADIO_HAL_VERSION_1_1)) {
             RILRequest rr = obtainRequest(RIL_REQUEST_STOP_KEEPALIVE, result,
                     mRILDefaultWorkSource);
 
@@ -4620,7 +4809,7 @@
             try {
                 dataProxy.stopKeepalive(rr.mSerial, sessionHandle);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(DATA_SERVICE, "stopNattKeepalive", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "stopNattKeepalive", e);
             }
         } else {
             if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "stopNattKeepalive: REQUEST_NOT_SUPPORTED");
@@ -4669,7 +4858,7 @@
     public void enableUiccApplications(boolean enable, Message result) {
         RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
         if (simProxy.isEmpty()) return;
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
+        if (mHalVersion.get(HAL_SERVICE_SIM).greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
             RILRequest rr = obtainRequest(RIL_REQUEST_ENABLE_UICC_APPLICATIONS, result,
                     mRILDefaultWorkSource);
 
@@ -4681,7 +4870,7 @@
             try {
                 simProxy.enableUiccApplications(rr.mSerial, enable);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "enableUiccApplications", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "enableUiccApplications", e);
             }
         } else {
             if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "enableUiccApplications: REQUEST_NOT_SUPPORTED");
@@ -4702,7 +4891,7 @@
     public void areUiccApplicationsEnabled(Message result) {
         RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
         if (simProxy.isEmpty()) return;
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
+        if (mHalVersion.get(HAL_SERVICE_SIM).greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
             RILRequest rr = obtainRequest(RIL_REQUEST_GET_UICC_APPLICATIONS_ENABLEMENT, result,
                     mRILDefaultWorkSource);
 
@@ -4713,7 +4902,7 @@
             try {
                 simProxy.areUiccApplicationsEnabled(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "areUiccApplicationsEnabled", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "areUiccApplicationsEnabled", e);
             }
         } else {
             if (RILJ_LOGD) {
@@ -4733,7 +4922,7 @@
     @Override
     public boolean canToggleUiccApplicationsEnablement() {
         return !getRadioServiceProxy(RadioSimProxy.class, null).isEmpty()
-                && mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5);
+                && mHalVersion.get(HAL_SERVICE_SIM).greaterOrEqual(RADIO_HAL_VERSION_1_5);
     }
 
     @Override
@@ -4759,7 +4948,7 @@
                 voiceProxy.handleStkCallSetupRequestFromSim(rr.mSerial, accept);
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(
-                        VOICE_SERVICE, "handleStkCallSetupRequestFromSim", e);
+                        HAL_SERVICE_VOICE, "handleStkCallSetupRequestFromSim", e);
             }
         }
     }
@@ -4771,7 +4960,7 @@
     public void getBarringInfo(Message result) {
         RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
         if (networkProxy.isEmpty()) return;
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
+        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_1_5)) {
             RILRequest rr = obtainRequest(RIL_REQUEST_GET_BARRING_INFO, result,
                     mRILDefaultWorkSource);
 
@@ -4782,7 +4971,7 @@
             try {
                 networkProxy.getBarringInfo(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getBarringInfo", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getBarringInfo", e);
             }
         } else {
             if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "getBarringInfo: REQUEST_NOT_SUPPORTED");
@@ -4801,7 +4990,7 @@
     public void allocatePduSessionId(Message result) {
         RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result);
         if (dataProxy.isEmpty()) return;
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
+        if (mHalVersion.get(HAL_SERVICE_DATA).greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
             RILRequest rr = obtainRequest(RIL_REQUEST_ALLOCATE_PDU_SESSION_ID, result,
                     mRILDefaultWorkSource);
             if (RILJ_LOGD) {
@@ -4811,7 +5000,7 @@
             try {
                 dataProxy.allocatePduSessionId(rr.mSerial);
             } catch (RemoteException e) {
-                handleRadioProxyExceptionForRR(DATA_SERVICE, "allocatePduSessionId", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "allocatePduSessionId", e);
             }
         } else {
             AsyncResult.forMessage(result, null,
@@ -4827,7 +5016,7 @@
     public void releasePduSessionId(Message result, int pduSessionId) {
         RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result);
         if (dataProxy.isEmpty()) return;
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
+        if (mHalVersion.get(HAL_SERVICE_DATA).greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
             RILRequest rr = obtainRequest(RIL_REQUEST_RELEASE_PDU_SESSION_ID, result,
                     mRILDefaultWorkSource);
             if (RILJ_LOGD) {
@@ -4837,7 +5026,7 @@
             try {
                 dataProxy.releasePduSessionId(rr.mSerial, pduSessionId);
             } catch (RemoteException e) {
-                handleRadioProxyExceptionForRR(DATA_SERVICE, "releasePduSessionId", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "releasePduSessionId", e);
             }
         } else {
             AsyncResult.forMessage(result, null,
@@ -4853,7 +5042,7 @@
     public void startHandover(Message result, int callId) {
         RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result);
         if (dataProxy.isEmpty()) return;
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
+        if (mHalVersion.get(HAL_SERVICE_DATA).greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
             RILRequest rr = obtainRequest(RIL_REQUEST_START_HANDOVER, result,
                     mRILDefaultWorkSource);
             if (RILJ_LOGD) {
@@ -4863,7 +5052,7 @@
             try {
                 dataProxy.startHandover(rr.mSerial, callId);
             } catch (RemoteException e) {
-                handleRadioProxyExceptionForRR(DATA_SERVICE, "startHandover", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "startHandover", e);
             }
         } else {
             if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "startHandover: REQUEST_NOT_SUPPORTED");
@@ -4882,7 +5071,7 @@
     public void cancelHandover(Message result, int callId) {
         RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result);
         if (dataProxy.isEmpty()) return;
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
+        if (mHalVersion.get(HAL_SERVICE_DATA).greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
             RILRequest rr = obtainRequest(RIL_REQUEST_CANCEL_HANDOVER, result,
                     mRILDefaultWorkSource);
             if (RILJ_LOGD) {
@@ -4892,7 +5081,7 @@
             try {
                 dataProxy.cancelHandover(rr.mSerial, callId);
             } catch (RemoteException e) {
-                handleRadioProxyExceptionForRR(DATA_SERVICE, "cancelHandover", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "cancelHandover", e);
             }
         } else {
             if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "cancelHandover: REQUEST_NOT_SUPPORTED");
@@ -4909,7 +5098,7 @@
     public void getSlicingConfig(Message result) {
         RadioDataProxy dataProxy = getRadioServiceProxy(RadioDataProxy.class, result);
         if (dataProxy.isEmpty()) return;
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
+        if (mHalVersion.get(HAL_SERVICE_DATA).greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
             RILRequest rr = obtainRequest(RIL_REQUEST_GET_SLICING_CONFIG, result,
                     mRILDefaultWorkSource);
 
@@ -4920,7 +5109,7 @@
             try {
                 dataProxy.getSlicingConfig(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(DATA_SERVICE, "getSlicingConfig", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "getSlicingConfig", e);
             }
         } else {
             if (RILJ_LOGD) Rlog.d(RILJ_LOG_TAG, "getSlicingConfig: REQUEST_NOT_SUPPORTED");
@@ -4934,7 +5123,7 @@
     public void getSimPhonebookRecords(Message result) {
         RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
         if (simProxy.isEmpty()) return;
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
+        if (mHalVersion.get(HAL_SERVICE_SIM).greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
             RILRequest rr = obtainRequest(RIL_REQUEST_GET_SIM_PHONEBOOK_RECORDS, result,
                     mRILDefaultWorkSource);
 
@@ -4945,7 +5134,7 @@
             try {
                 simProxy.getSimPhonebookRecords(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "getSimPhonebookRecords", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "getSimPhonebookRecords", e);
             }
         } else {
             if (RILJ_LOGD) {
@@ -4963,7 +5152,7 @@
     public void getSimPhonebookCapacity(Message result) {
         RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
         if (simProxy.isEmpty()) return;
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
+        if (mHalVersion.get(HAL_SERVICE_SIM).greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
             RILRequest rr = obtainRequest(RIL_REQUEST_GET_SIM_PHONEBOOK_CAPACITY, result,
                     mRILDefaultWorkSource);
 
@@ -4974,7 +5163,7 @@
             try {
                 simProxy.getSimPhonebookCapacity(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "getSimPhonebookCapacity", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "getSimPhonebookCapacity", e);
             }
         } else {
             if (RILJ_LOGD) {
@@ -4992,7 +5181,7 @@
     public void updateSimPhonebookRecord(SimPhonebookRecord phonebookRecord, Message result) {
         RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class, result);
         if (simProxy.isEmpty()) return;
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
+        if (mHalVersion.get(HAL_SERVICE_SIM).greaterOrEqual(RADIO_HAL_VERSION_1_6)) {
             RILRequest rr = obtainRequest(RIL_REQUEST_UPDATE_SIM_PHONEBOOK_RECORD, result,
                     mRILDefaultWorkSource);
 
@@ -5004,7 +5193,7 @@
             try {
                 simProxy.updateSimPhonebookRecords(rr.mSerial, phonebookRecord);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "updateSimPhonebookRecords", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "updateSimPhonebookRecords", e);
             }
         } else {
             if (RILJ_LOGD) {
@@ -5029,7 +5218,7 @@
             /* @TelephonyManager.UsageSetting */ int usageSetting) {
         RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
         if (networkProxy.isEmpty()) return;
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
+        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
             RILRequest rr = obtainRequest(RIL_REQUEST_SET_USAGE_SETTING, result,
                     mRILDefaultWorkSource);
 
@@ -5040,7 +5229,7 @@
             try {
                 networkProxy.setUsageSetting(rr.mSerial, usageSetting);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setUsageSetting", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setUsageSetting", e);
             }
         } else {
             if (RILJ_LOGD) {
@@ -5063,7 +5252,7 @@
     public void getUsageSetting(Message result) {
         RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
         if (networkProxy.isEmpty()) return;
-        if (mRadioVersion.greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
+        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
             RILRequest rr = obtainRequest(RIL_REQUEST_GET_USAGE_SETTING, result,
                     mRILDefaultWorkSource);
 
@@ -5074,7 +5263,7 @@
             try {
                 networkProxy.getUsageSetting(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getUsageSetting", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getUsageSetting", e);
             }
         } else {
             if (RILJ_LOGD) {
@@ -5088,6 +5277,783 @@
         }
     }
 
+    @Override
+    public void setSrvccCallInfo(SrvccConnection[] srvccConnections, Message result) {
+        RadioImsProxy imsProxy = getRadioServiceProxy(RadioImsProxy.class, result);
+        if (imsProxy.isEmpty()) return;
+        if (mHalVersion.get(HAL_SERVICE_IMS).greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
+            RILRequest rr = obtainRequest(RIL_REQUEST_SET_SRVCC_CALL_INFO, result,
+                    mRILDefaultWorkSource);
+
+            if (RILJ_LOGD) {
+                // Do not log function arg for privacy
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
+
+            try {
+                imsProxy.setSrvccCallInfo(rr.mSerial,
+                        RILUtils.convertToHalSrvccCall(srvccConnections));
+            } catch (RemoteException | RuntimeException e) {
+                handleRadioProxyExceptionForRR(HAL_SERVICE_IMS, "setSrvccCallInfo", e);
+            }
+        } else {
+            if (RILJ_LOGD) {
+                Rlog.d(RILJ_LOG_TAG, "setSrvccCallInfo: REQUEST_NOT_SUPPORTED");
+            }
+            if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
+            }
+        }
+    }
+
+    @Override
+    public void updateImsRegistrationInfo(
+            @RegistrationManager.ImsRegistrationState int state,
+            @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech,
+            @RegistrationManager.SuggestedAction int suggestedAction,
+            int capabilities, Message result) {
+        RadioImsProxy imsProxy = getRadioServiceProxy(RadioImsProxy.class, result);
+        if (imsProxy.isEmpty()) return;
+        if (mHalVersion.get(HAL_SERVICE_IMS).greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
+            RILRequest rr = obtainRequest(RIL_REQUEST_UPDATE_IMS_REGISTRATION_INFO, result,
+                    mRILDefaultWorkSource);
+
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                        + " state=" + state + ", radioTech=" + imsRadioTech
+                        + ", suggested=" + suggestedAction + ", cap=" + capabilities);
+            }
+
+            android.hardware.radio.ims.ImsRegistration registrationInfo =
+                    new android.hardware.radio.ims.ImsRegistration();
+            registrationInfo.regState = RILUtils.convertImsRegistrationState(state);
+            registrationInfo.accessNetworkType = RILUtils.convertImsRegistrationTech(imsRadioTech);
+            registrationInfo.suggestedAction = suggestedAction;
+            registrationInfo.capabilities = RILUtils.convertImsCapability(capabilities);
+
+            try {
+                imsProxy.updateImsRegistrationInfo(rr.mSerial, registrationInfo);
+            } catch (RemoteException | RuntimeException e) {
+                handleRadioProxyExceptionForRR(HAL_SERVICE_IMS, "updateImsRegistrationInfo", e);
+            }
+        } else {
+            if (RILJ_LOGD) {
+                Rlog.d(RILJ_LOG_TAG, "updateImsRegistrationInfo: REQUEST_NOT_SUPPORTED");
+            }
+            if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
+            }
+        }
+    }
+
+    @Override
+    public void startImsTraffic(int token,
+            int trafficType, int accessNetworkType, int trafficDirection, Message result) {
+        RadioImsProxy imsProxy = getRadioServiceProxy(RadioImsProxy.class, result);
+        if (imsProxy.isEmpty()) return;
+        if (mHalVersion.get(HAL_SERVICE_IMS).greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
+            RILRequest rr = obtainRequest(RIL_REQUEST_START_IMS_TRAFFIC, result,
+                    mRILDefaultWorkSource);
+
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                        + "{" + token + ", " + trafficType + ", "
+                        + accessNetworkType + ", " + trafficDirection + "}");
+            }
+
+            try {
+                imsProxy.startImsTraffic(rr.mSerial, token,
+                        RILUtils.convertImsTrafficType(trafficType), accessNetworkType,
+                        RILUtils.convertImsTrafficDirection(trafficDirection));
+            } catch (RemoteException | RuntimeException e) {
+                handleRadioProxyExceptionForRR(HAL_SERVICE_IMS, "startImsTraffic", e);
+            }
+        } else {
+            if (RILJ_LOGD) {
+                Rlog.d(RILJ_LOG_TAG, "startImsTraffic: REQUEST_NOT_SUPPORTED");
+            }
+            if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
+            }
+        }
+    }
+
+    @Override
+    public void stopImsTraffic(int token, Message result) {
+        RadioImsProxy imsProxy = getRadioServiceProxy(RadioImsProxy.class, result);
+        if (imsProxy.isEmpty()) return;
+        if (mHalVersion.get(HAL_SERVICE_IMS).greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
+            RILRequest rr = obtainRequest(RIL_REQUEST_STOP_IMS_TRAFFIC, result,
+                    mRILDefaultWorkSource);
+
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                        + "{" + token + "}");
+            }
+
+            try {
+                imsProxy.stopImsTraffic(rr.mSerial, token);
+            } catch (RemoteException | RuntimeException e) {
+                handleRadioProxyExceptionForRR(HAL_SERVICE_IMS, "stopImsTraffic", e);
+            }
+        } else {
+            if (RILJ_LOGD) {
+                Rlog.d(RILJ_LOG_TAG, "stopImsTraffic: REQUEST_NOT_SUPPORTED");
+            }
+            if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
+            }
+        }
+    }
+
+    @Override
+    public void triggerEpsFallback(int reason, Message result) {
+        RadioImsProxy imsProxy = getRadioServiceProxy(RadioImsProxy.class, result);
+        if (imsProxy.isEmpty()) return;
+        if (mHalVersion.get(HAL_SERVICE_IMS).greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
+            RILRequest rr = obtainRequest(RIL_REQUEST_TRIGGER_EPS_FALLBACK, result,
+                    mRILDefaultWorkSource);
+
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                        + " reason=" + reason);
+            }
+
+            try {
+                imsProxy.triggerEpsFallback(rr.mSerial, reason);
+            } catch (RemoteException | RuntimeException e) {
+                handleRadioProxyExceptionForRR(HAL_SERVICE_IMS, "triggerEpsFallback", e);
+            }
+        } else {
+            if (RILJ_LOGD) {
+                Rlog.d(RILJ_LOG_TAG, "triggerEpsFallback: REQUEST_NOT_SUPPORTED");
+            }
+            if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
+            }
+        }
+    }
+
+    @Override
+    public void sendAnbrQuery(int mediaType, int direction, int bitsPerSecond,
+            Message result) {
+        RadioImsProxy imsProxy = getRadioServiceProxy(RadioImsProxy.class, result);
+        if (imsProxy.isEmpty()) return;
+        if (mHalVersion.get(HAL_SERVICE_IMS).greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
+            RILRequest rr = obtainRequest(RIL_REQUEST_SEND_ANBR_QUERY, result,
+                    mRILDefaultWorkSource);
+
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
+
+            try {
+                imsProxy.sendAnbrQuery(rr.mSerial, mediaType, direction, bitsPerSecond);
+            } catch (RemoteException | RuntimeException e) {
+                handleRadioProxyExceptionForRR(HAL_SERVICE_IMS, "sendAnbrQuery", e);
+            }
+        } else {
+            if (RILJ_LOGD) {
+                Rlog.d(RILJ_LOG_TAG, "sendAnbrQuery: REQUEST_NOT_SUPPORTED");
+            }
+            if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setEmergencyMode(int emcMode, Message result) {
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
+        if (networkProxy.isEmpty()) return;
+        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1)) {
+            RILRequest rr = obtainRequest(RIL_REQUEST_SET_EMERGENCY_MODE, result,
+                    mRILDefaultWorkSource);
+
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                        + " mode=" + EmergencyConstants.emergencyModeToString(emcMode));
+            }
+
+            try {
+                networkProxy.setEmergencyMode(rr.mSerial, emcMode);
+            } catch (RemoteException | RuntimeException e) {
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setEmergencyMode", e);
+            }
+        } else {
+            if (RILJ_LOGD) {
+                Rlog.d(RILJ_LOG_TAG, "setEmergencyMode: REQUEST_NOT_SUPPORTED");
+            }
+            if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void triggerEmergencyNetworkScan(
+            @NonNull @AccessNetworkConstants.RadioAccessNetworkType int[] accessNetwork,
+            @DomainSelectionService.EmergencyScanType int scanType, Message result) {
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
+        if (networkProxy.isEmpty()) return;
+        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1)) {
+            RILRequest rr = obtainRequest(RIL_REQUEST_TRIGGER_EMERGENCY_NETWORK_SCAN, result,
+                    mRILDefaultWorkSource);
+
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                        + " networkType=" + RILUtils.accessNetworkTypesToString(accessNetwork)
+                        + ", scanType=" + RILUtils.scanTypeToString(scanType));
+            }
+
+            try {
+                networkProxy.triggerEmergencyNetworkScan(rr.mSerial,
+                        RILUtils.convertEmergencyNetworkScanTrigger(accessNetwork, scanType));
+            } catch (RemoteException | RuntimeException e) {
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK,
+                        "triggerEmergencyNetworkScan", e);
+            }
+        } else {
+            if (RILJ_LOGD) {
+                Rlog.d(RILJ_LOG_TAG, "triggerEmergencyNetworkScan: REQUEST_NOT_SUPPORTED");
+            }
+            if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void cancelEmergencyNetworkScan(boolean resetScan, Message result) {
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
+        if (networkProxy.isEmpty()) return;
+        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1)) {
+            RILRequest rr = obtainRequest(RIL_REQUEST_CANCEL_EMERGENCY_NETWORK_SCAN, result,
+                    mRILDefaultWorkSource);
+
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                        + " resetScan=" + resetScan);
+            }
+
+            try {
+                networkProxy.cancelEmergencyNetworkScan(rr.mSerial, resetScan);
+            } catch (RemoteException | RuntimeException e) {
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK,
+                        "cancelEmergencyNetworkScan", e);
+            }
+        } else {
+            if (RILJ_LOGD) {
+                Rlog.d(RILJ_LOG_TAG, "cancelEmergencyNetworkScan: REQUEST_NOT_SUPPORTED");
+            }
+            if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void exitEmergencyMode(Message result) {
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
+        if (networkProxy.isEmpty()) return;
+        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1)) {
+            RILRequest rr = obtainRequest(RIL_REQUEST_EXIT_EMERGENCY_MODE, result,
+                    mRILDefaultWorkSource);
+
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
+
+            try {
+                networkProxy.exitEmergencyMode(rr.mSerial);
+            } catch (RemoteException | RuntimeException e) {
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "exitEmergencyMode", e);
+            }
+        } else {
+            if (RILJ_LOGD) {
+                Rlog.d(RILJ_LOG_TAG, "exitEmergencyMode: REQUEST_NOT_SUPPORTED");
+            }
+            if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
+            }
+        }
+    }
+
+    /**
+     * Set if null ciphering / null integrity modes are permitted.
+     *
+     * @param result Callback message containing the success or failure status.
+     * @param enabled true if null ciphering / null integrity modes are permitted, false otherwise
+     */
+    @Override
+    public void setNullCipherAndIntegrityEnabled(boolean enabled, Message result) {
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
+        if (networkProxy.isEmpty()) return;
+        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1)) {
+            RILRequest rr = obtainRequest(RIL_REQUEST_SET_NULL_CIPHER_AND_INTEGRITY_ENABLED, result,
+                    mRILDefaultWorkSource);
+
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
+
+            try {
+                networkProxy.setNullCipherAndIntegrityEnabled(rr.mSerial, enabled);
+            } catch (RemoteException | RuntimeException e) {
+                handleRadioProxyExceptionForRR(
+                        HAL_SERVICE_NETWORK, "setNullCipherAndIntegrityEnabled", e);
+            }
+        } else {
+            if (RILJ_LOGD) {
+                Rlog.d(RILJ_LOG_TAG, "setNullCipherAndIntegrityEnabled: REQUEST_NOT_SUPPORTED");
+            }
+            if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
+            }
+        }
+    }
+
+    /**
+     * Get if null ciphering / null integrity are enabled / disabled.
+     *
+     * @param result Callback message containing the success or failure status.
+     */
+    @Override
+    public void isNullCipherAndIntegrityEnabled(Message result) {
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
+        if (networkProxy.isEmpty()) return;
+        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1)) {
+            RILRequest rr = obtainRequest(RIL_REQUEST_IS_NULL_CIPHER_AND_INTEGRITY_ENABLED, result,
+                    mRILDefaultWorkSource);
+
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
+
+            try {
+                networkProxy.isNullCipherAndIntegrityEnabled(rr.mSerial);
+            } catch (RemoteException | RuntimeException e) {
+                handleRadioProxyExceptionForRR(
+                        HAL_SERVICE_NETWORK, "isNullCipherAndIntegrityEnabled", e);
+            }
+        } else {
+            if (RILJ_LOGD) {
+                Rlog.d(RILJ_LOG_TAG, "isNullCipherAndIntegrityEnabled: REQUEST_NOT_SUPPORTED");
+            }
+            if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void updateImsCallStatus(@NonNull List<ImsCallInfo> imsCallInfo, Message result) {
+        RadioImsProxy imsProxy = getRadioServiceProxy(RadioImsProxy.class, result);
+        if (imsProxy.isEmpty()) return;
+        if (mHalVersion.get(HAL_SERVICE_IMS).greaterOrEqual(RADIO_HAL_VERSION_2_0)) {
+            RILRequest rr = obtainRequest(RIL_REQUEST_UPDATE_IMS_CALL_STATUS, result,
+                    mRILDefaultWorkSource);
+
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                        + " " + imsCallInfo);
+            }
+            try {
+                imsProxy.updateImsCallStatus(rr.mSerial, RILUtils.convertImsCallInfo(imsCallInfo));
+            } catch (RemoteException | RuntimeException e) {
+                handleRadioProxyExceptionForRR(HAL_SERVICE_IMS, "updateImsCallStatus", e);
+            }
+        } else {
+            if (RILJ_LOGD) {
+                Rlog.d(RILJ_LOG_TAG, "updateImsCallStatus: REQUEST_NOT_SUPPORTED");
+            }
+            if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setN1ModeEnabled(boolean enable, Message result) {
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
+        if (networkProxy.isEmpty()) return;
+        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1)) {
+            RILRequest rr = obtainRequest(RIL_REQUEST_SET_N1_MODE_ENABLED, result,
+                    mRILDefaultWorkSource);
+
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                        + " enable=" + enable);
+            }
+
+            try {
+                networkProxy.setN1ModeEnabled(rr.mSerial, enable);
+            } catch (RemoteException | RuntimeException e) {
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setN1ModeEnabled", e);
+            }
+        } else {
+            if (RILJ_LOGD) {
+                Rlog.d(RILJ_LOG_TAG, "setN1ModeEnabled: REQUEST_NOT_SUPPORTED");
+            }
+            if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void isN1ModeEnabled(Message result) {
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class, result);
+        if (networkProxy.isEmpty()) return;
+        if (mHalVersion.get(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1)) {
+            RILRequest rr = obtainRequest(RIL_REQUEST_IS_N1_MODE_ENABLED, result,
+                    mRILDefaultWorkSource);
+
+            if (RILJ_LOGD) {
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            }
+
+            try {
+                networkProxy.isN1ModeEnabled(rr.mSerial);
+            } catch (RemoteException | RuntimeException e) {
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "isN1ModeEnabled", e);
+            }
+        } else {
+            if (RILJ_LOGD) {
+                Rlog.d(RILJ_LOG_TAG, "isN1ModeEnabled: REQUEST_NOT_SUPPORTED");
+            }
+            if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
+            }
+        }
+    }
+
+    /**
+     * Get feature capabilities supported by satellite.
+     *
+     * @param result Message that will be sent back to the requester
+     */
+    @Override
+    public void getSatelliteCapabilities(Message result) {
+        // Satellite HAL APIs are not supported before Android V.
+        if (result != null) {
+            AsyncResult.forMessage(result, null,
+                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+            result.sendToTarget();
+        }
+    }
+
+    /**
+     * Turn satellite modem on/off.
+     *
+     * @param result Message that will be sent back to the requester
+     * @param on True for turning on.
+     *           False for turning off.
+     */
+    @Override
+    public void setSatellitePower(Message result, boolean on) {
+        // Satellite HAL APIs are not supported before Android V.
+        if (result != null) {
+            AsyncResult.forMessage(result, null,
+                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+            result.sendToTarget();
+        }
+    }
+
+    /**
+     * Get satellite modem state.
+     *
+     * @param result Message that will be sent back to the requester
+     */
+    @Override
+    public void getSatellitePowerState(Message result) {
+        // Satellite HAL APIs are not supported before Android V.
+        if (result != null) {
+            AsyncResult.forMessage(result, null,
+                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+            result.sendToTarget();
+        }
+    }
+
+    /**
+     * Get satellite provision state.
+     *
+     * @param result Message that will be sent back to the requester
+     */
+    @Override
+    public void getSatelliteProvisionState(Message result) {
+        // Satellite HAL APIs are not supported before Android V.
+        if (result != null) {
+            AsyncResult.forMessage(result, null,
+                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+            result.sendToTarget();
+        }
+    }
+
+    /**
+     * Provision the subscription with a satellite provider. This is needed to register the
+     * subscription if the provider allows dynamic registration.
+     *
+     * @param result Message that will be sent back to the requester.
+     * @param imei IMEI of the SIM associated with the satellite modem.
+     * @param msisdn MSISDN of the SIM associated with the satellite modem.
+     * @param imsi IMSI of the SIM associated with the satellite modem.
+     * @param features List of features to be provisioned.
+     */
+    @Override
+    public void provisionSatelliteService(
+            Message result, String imei, String msisdn, String imsi, int[] features) {
+        // Satellite HAL APIs are not supported before Android V.
+        if (result != null) {
+            AsyncResult.forMessage(result, null,
+                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+            result.sendToTarget();
+        }
+    }
+
+    /**
+     * Add contacts that are allowed to be used for satellite communication. This is applicable for
+     * incoming messages as well.
+     *
+     * @param result Message that will be sent back to the requester.
+     * @param contacts List of allowed contacts to be added.
+     */
+    @Override
+    public void addAllowedSatelliteContacts(Message result, String[] contacts) {
+        // Satellite HAL APIs are not supported before Android V.
+        if (result != null) {
+            AsyncResult.forMessage(result, null,
+                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+            result.sendToTarget();
+        }
+    }
+
+    /**
+     * Remove contacts that are allowed to be used for satellite communication. This is applicable
+     * for incoming messages as well.
+     *
+     * @param result Message that will be sent back to the requester.
+     * @param contacts List of allowed contacts to be removed.
+     */
+    @Override
+    public void removeAllowedSatelliteContacts(Message result, String[] contacts) {
+        // Satellite HAL APIs are not supported before Android V.
+        if (result != null) {
+            AsyncResult.forMessage(result, null,
+                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+            result.sendToTarget();
+        }
+    }
+
+    /**
+     * Send text messages.
+     *
+     * @param result Message that will be sent back to the requester.
+     * @param messages List of messages in text format to be sent.
+     * @param destination The recipient of the message.
+     * @param latitude The current latitude of the device.
+     * @param longitude The current longitude of the device.
+     */
+    @Override
+    public void sendSatelliteMessages(Message result, String[] messages, String destination,
+            double latitude, double longitude) {
+        // Satellite HAL APIs are not supported before Android V.
+        if (result != null) {
+            AsyncResult.forMessage(result, null,
+                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+            result.sendToTarget();
+        }
+    }
+
+    /**
+     * Get pending messages.
+     *
+     * @param result Message that will be sent back to the requester.
+     */
+    @Override
+    public void getPendingSatelliteMessages(Message result) {
+        // Satellite HAL APIs are not supported before Android V.
+        if (result != null) {
+            AsyncResult.forMessage(result, null,
+                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+            result.sendToTarget();
+        }
+    }
+
+    /**
+     * Get current satellite registration mode.
+     *
+     * @param result Message that will be sent back to the requester.
+     */
+    @Override
+    public void getSatelliteMode(Message result) {
+        // Satellite HAL APIs are not supported before Android V.
+        if (result != null) {
+            AsyncResult.forMessage(result, null,
+                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+            result.sendToTarget();
+        }
+    }
+
+    /**
+     * Set the filter for what type of indication framework want to receive from modem.
+     *
+     * @param result Message that will be sent back to the requester.
+     * @param filterBitmask The filter bitmask identifying what type of indication framework want to
+     *                         receive from modem.
+     */
+    @Override
+    public void setSatelliteIndicationFilter(Message result, int filterBitmask) {
+        // Satellite HAL APIs are not supported before Android V.
+        if (result != null) {
+            AsyncResult.forMessage(result, null,
+                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+            result.sendToTarget();
+        }
+    }
+
+    /**
+     * Check whether satellite modem is supported by the device.
+     *
+     * @param result Message that will be sent back to the requester.
+     */
+    @Override
+    public void isSatelliteSupported(Message result) {
+        // Satellite HAL APIs are not supported before Android V.
+        if (result != null) {
+            AsyncResult.forMessage(result, null,
+                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+            result.sendToTarget();
+        }
+    }
+
+    /**
+     * User started pointing to the satellite. Modem should continue to update the ponting input
+     * as user moves device.
+     *
+     * @param result Message that will be sent back to the requester.
+     */
+    @Override
+    public void startSendingSatellitePointingInfo(Message result) {
+        // Satellite HAL APIs are not supported before Android V.
+        if (result != null) {
+            AsyncResult.forMessage(result, null,
+                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+            result.sendToTarget();
+        }
+    }
+
+    /**
+     * Stop pointing to satellite indications.
+     *
+     * @param result Message that will be sent back to the requester.
+     */
+    @Override
+    public void stopSendingSatellitePointingInfo(Message result) {
+        // Satellite HAL APIs are not supported before Android V.
+        if (result != null) {
+            AsyncResult.forMessage(result, null,
+                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+            result.sendToTarget();
+        }
+    }
+
+    /**
+     * Get max text limit for messaging per message.
+     *
+     * @param result Message that will be sent back to the requester.
+     */
+    @Override
+    public void getMaxCharactersPerSatelliteTextMessage(Message result) {
+        // Satellite HAL APIs are not supported before Android V.
+        if (result != null) {
+            AsyncResult.forMessage(result, null,
+                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+            result.sendToTarget();
+        }
+    }
+
+    /**
+     * Get whether satellite communication is allowed for the current location
+     *
+     * @param result Message that will be sent back to the requester.
+     */
+    @Override
+    public void isSatelliteCommunicationAllowedForCurrentLocation(Message result) {
+        // Satellite HAL APIs are not supported before Android V.
+        if (result != null) {
+            AsyncResult.forMessage(result, null,
+                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+            result.sendToTarget();
+        }
+    }
+
+    /**
+     * Get time for next visibility of satellite.
+     *
+     * @param result Message that will be sent back to the requester.
+     */
+    @Override
+    public void getTimeForNextSatelliteVisibility(Message result) {
+        // Satellite HAL APIs are not supported before Android V.
+        if (result != null) {
+            AsyncResult.forMessage(result, null,
+                    CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+            result.sendToTarget();
+        }
+    }
+
     //***** Private Methods
     /**
      * This is a helper function to be called when an indication callback is called for any radio
@@ -5129,7 +6095,7 @@
      */
     @VisibleForTesting
     public RILRequest processResponse(RadioResponseInfo responseInfo) {
-        return processResponseInternal(RADIO_SERVICE, responseInfo.serial, responseInfo.error,
+        return processResponseInternal(HAL_SERVICE_RADIO, responseInfo.serial, responseInfo.error,
                 responseInfo.type);
     }
 
@@ -5143,7 +6109,7 @@
     @VisibleForTesting
     public RILRequest processResponse_1_6(
             android.hardware.radio.V1_6.RadioResponseInfo responseInfo) {
-        return processResponseInternal(RADIO_SERVICE, responseInfo.serial, responseInfo.error,
+        return processResponseInternal(HAL_SERVICE_RADIO, responseInfo.serial, responseInfo.error,
                 responseInfo.type);
     }
 
@@ -5155,7 +6121,6 @@
      * @param responseInfo RadioResponseInfo received in response callback
      * @return RILRequest corresponding to the response
      */
-    @VisibleForTesting
     public RILRequest processResponse(int service,
             android.hardware.radio.RadioResponseInfo responseInfo) {
         return processResponseInternal(service, responseInfo.serial, responseInfo.error,
@@ -5190,7 +6155,7 @@
                     + " ,error: " + error);
             return null;
         }
-        Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_NETWORK, "RIL", "" /* unused */, rr.mSerial);
+        Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_NETWORK, "RIL", rr.mSerial);
 
         // Time logging for RIL command and storing it in TelephonyHistogram.
         addToRilHistogram(rr);
@@ -5355,13 +6320,13 @@
         RILRequest rr = RILRequest.obtain(RIL_RESPONSE_ACKNOWLEDGEMENT, null,
                 mRILDefaultWorkSource);
         acquireWakeLock(rr, FOR_ACK_WAKELOCK);
-        if (service == RADIO_SERVICE) {
+        if (service == HAL_SERVICE_RADIO) {
             IRadio radioProxy = getRadioProxy(null);
             if (radioProxy != null) {
                 try {
                     radioProxy.responseAcknowledgement();
                 } catch (RemoteException | RuntimeException e) {
-                    handleRadioProxyExceptionForRR(RADIO_SERVICE, "sendAck", e);
+                    handleRadioProxyExceptionForRR(HAL_SERVICE_RADIO, "sendAck", e);
                     riljLoge("sendAck: " + e);
                 }
             } else {
@@ -5506,7 +6471,7 @@
             synchronized (mWakeLock) {
                 if (mWakeLockCount == 0 && !mWakeLock.isHeld()) return false;
                 Rlog.d(RILJ_LOG_TAG, "NOTE: mWakeLockCount is " + mWakeLockCount
-                        + "at time of clearing");
+                        + " at time of clearing");
                 mWakeLockCount = 0;
                 mWakeLock.release();
                 mClientWakelockTracker.stopTrackingAll();
@@ -5679,6 +6644,22 @@
                 sb.append("[").append(hwcfg).append("] ");
             }
             s = sb.toString();
+        } else if (req == RIL_REQUEST_START_IMS_TRAFFIC
+                || req == RIL_UNSOL_CONNECTION_SETUP_FAILURE) {
+            sb = new StringBuilder("{");
+            Object[] info = (Object[]) ret;
+            int token = (Integer) info[0];
+            sb.append(token).append(", ");
+            if (info[1] != null) {
+                ConnectionFailureInfo failureInfo = (ConnectionFailureInfo) info[1];
+                sb.append(failureInfo.getReason()).append(", ");
+                sb.append(failureInfo.getCauseCode()).append(", ");
+                sb.append(failureInfo.getWaitTimeMillis());
+            } else {
+                sb.append("null");
+            }
+            sb.append("}");
+            s = sb.toString();
         } else {
             // Check if toString() was overridden. Java classes created from HIDL have a built-in
             // toString() method, but AIDL classes only have it if the parcelable contains a
@@ -5856,6 +6837,13 @@
 
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("RIL: " + this);
+        pw.println(" " + mServiceProxies.get(HAL_SERVICE_DATA));
+        pw.println(" " + mServiceProxies.get(HAL_SERVICE_MESSAGING));
+        pw.println(" " + mServiceProxies.get(HAL_SERVICE_MODEM));
+        pw.println(" " + mServiceProxies.get(HAL_SERVICE_NETWORK));
+        pw.println(" " + mServiceProxies.get(HAL_SERVICE_SIM));
+        pw.println(" " + mServiceProxies.get(HAL_SERVICE_VOICE));
+        pw.println(" " + mServiceProxies.get(HAL_SERVICE_IMS));
         pw.println(" mWakeLock=" + mWakeLock);
         pw.println(" mWakeLockTimeout=" + mWakeLockTimeout);
         synchronized (mRequestList) {
@@ -5931,31 +6919,60 @@
                 new CellSignalStrengthNr());
     }
 
-    /**
-     * Get the HAL version.
-     *
-     * @return the current HalVersion
-     */
-    public HalVersion getHalVersion() {
-        return mRadioVersion;
+    void notifyBarringInfoChanged(@NonNull BarringInfo barringInfo) {
+        mLastBarringInfo = barringInfo;
+        mBarringInfoChangedRegistrants.notifyRegistrants(new AsyncResult(null, barringInfo, null));
     }
 
-    private static String serviceToString(int service) {
+    /**
+     * Get the HAL version with a specific service.
+     *
+     * @param service the hal service id
+     * @return the current HalVersion
+     */
+    public HalVersion getHalVersion(int service) {
+        HalVersion halVersion = mHalVersion.get(service);
+        if (halVersion == null) {
+            if (isRadioServiceSupported(service)) {
+                halVersion = RADIO_HAL_VERSION_UNKNOWN;
+            } else {
+                halVersion = RADIO_HAL_VERSION_UNSUPPORTED;
+            }
+        }
+        return halVersion;
+    }
+
+    /**
+     * Get the HAL version corresponding to the interface version of a IRadioService module.
+     * @param interfaceVersion The interface version, from IRadioService#getInterfaceVersion().
+     * @return The corresponding HalVersion.
+     */
+    public static HalVersion getServiceHalVersion(int interfaceVersion) {
+        switch (interfaceVersion) {
+            case 1: return RADIO_HAL_VERSION_2_0;
+            case 2: return RADIO_HAL_VERSION_2_1;
+            default: return RADIO_HAL_VERSION_UNKNOWN;
+        }
+    }
+
+    private static String serviceToString(@HalService int service) {
         switch (service) {
-            case RADIO_SERVICE:
+            case HAL_SERVICE_RADIO:
                 return "RADIO";
-            case DATA_SERVICE:
+            case HAL_SERVICE_DATA:
                 return "DATA";
-            case MESSAGING_SERVICE:
+            case HAL_SERVICE_MESSAGING:
                 return "MESSAGING";
-            case MODEM_SERVICE:
+            case HAL_SERVICE_MODEM:
                 return "MODEM";
-            case NETWORK_SERVICE:
+            case HAL_SERVICE_NETWORK:
                 return "NETWORK";
-            case SIM_SERVICE:
+            case HAL_SERVICE_SIM:
                 return "SIM";
-            case VOICE_SERVICE:
+            case HAL_SERVICE_VOICE:
                 return "VOICE";
+            case HAL_SERVICE_IMS:
+                return "IMS";
             default:
                 return "UNKNOWN:" + service;
         }
diff --git a/src/java/com/android/internal/telephony/RILUtils.java b/src/java/com/android/internal/telephony/RILUtils.java
index 61f1e82..9db186f 100644
--- a/src/java/com/android/internal/telephony/RILUtils.java
+++ b/src/java/com/android/internal/telephony/RILUtils.java
@@ -29,6 +29,7 @@
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ALLOW_DATA;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ANSWER;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_BASEBAND_VERSION;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CANCEL_EMERGENCY_NETWORK_SCAN;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CANCEL_HANDOVER;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CANCEL_USSD;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CDMA_BROADCAST_ACTIVATION;
@@ -58,6 +59,7 @@
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DEACTIVATE_DATA_CALL;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DELETE_SMS_ON_SIM;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DEVICE_IDENTITY;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DEVICE_IMEI;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DIAL;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DTMF;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DTMF_START;
@@ -74,6 +76,7 @@
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ENTER_SIM_PUK;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ENTER_SIM_PUK2;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_EXIT_EMERGENCY_CALLBACK_MODE;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_EXIT_EMERGENCY_MODE;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_EXPLICIT_CALL_TRANSFER;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_ACTIVITY_INFO;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_GET_ALLOWED_CARRIERS;
@@ -112,7 +115,9 @@
 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_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_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;
@@ -138,6 +143,7 @@
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_RESET_RADIO;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SCREEN_STATE;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SEND_ANBR_QUERY;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SEND_DEVICE_STATE;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SEND_SMS;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SEND_SMS_EXPECT_MORE;
@@ -154,20 +160,24 @@
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_DATA_PROFILE;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_DATA_THROTTLING;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_DC_RT_INFO_RATE;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_EMERGENCY_MODE;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_FACILITY_LOCK;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_INITIAL_ATTACH_APN;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_LINK_CAPACITY_REPORTING_CRITERIA;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_LOCATION_UPDATES;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_MUTE;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_N1_MODE_ENABLED;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_NETWORK_SELECTION_AUTOMATIC;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_NETWORK_SELECTION_MANUAL;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_NULL_CIPHER_AND_INTEGRITY_ENABLED;
 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_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;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_SRVCC_CALL_INFO;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_SUPP_SVC_NOTIFICATION;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_SYSTEM_SELECTION_CHANNELS;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_TTY_MODE;
@@ -185,6 +195,7 @@
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SIM_TRANSMIT_APDU_CHANNEL;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SMS_ACKNOWLEDGE;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_START_HANDOVER;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_START_IMS_TRAFFIC;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_START_KEEPALIVE;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_START_LCE;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_START_NETWORK_SCAN;
@@ -194,12 +205,17 @@
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_STK_SEND_ENVELOPE_WITH_STATUS;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_STK_SET_PROFILE;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_STOP_IMS_TRAFFIC;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_STOP_KEEPALIVE;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_STOP_LCE;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_STOP_NETWORK_SCAN;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SWITCH_DUAL_SIM_CONFIG;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_TRIGGER_EMERGENCY_NETWORK_SCAN;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_TRIGGER_EPS_FALLBACK;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_UDUB;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_UPDATE_IMS_CALL_STATUS;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_UPDATE_IMS_REGISTRATION_INFO;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_UPDATE_SIM_PHONEBOOK_RECORD;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_VOICE_RADIO_TECH;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_VOICE_REGISTRATION_STATE;
@@ -214,8 +230,10 @@
 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_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;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_DC_RT_INFO_CHANGED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_EMERGENCY_NETWORK_SCAN_RESULT;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_EMERGENCY_NUMBER_LIST;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_ENTER_EMERGENCY_CALLBACK_MODE;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_EXIT_EMERGENCY_CALLBACK_MODE;
@@ -226,6 +244,7 @@
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_MODEM_RESTART;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_NETWORK_SCAN_RESULT;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_NITZ_TIME_RECEIVED;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_NOTIFY_ANBR;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_OEM_HOOK_RAW;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_ON_SS;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_ON_USSD;
@@ -253,6 +272,7 @@
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_SIGNAL_STRENGTH;
 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;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_SRVCC_STATE_NOTIFY;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_STK_CALL_SETUP;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_STK_CC_ALPHA_NOTIFY;
@@ -260,11 +280,13 @@
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_STK_PROACTIVE_COMMAND;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_STK_SESSION_END;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_SUPP_SVC_NOTIFICATION;
+import static com.android.internal.telephony.RILConstants.RIL_UNSOL_TRIGGER_IMS_DEREGISTRATION;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_UICC_APPLICATIONS_ENABLEMENT_CHANGED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_UICC_SUBSCRIPTION_STATUS_CHANGED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_UNTHROTTLE_APN;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_VOICE_RADIO_TECH_CHANGED;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.net.InetAddresses;
 import android.net.LinkAddress;
@@ -298,8 +320,11 @@
 import android.telephony.CellSignalStrengthTdscdma;
 import android.telephony.CellSignalStrengthWcdma;
 import android.telephony.ClosedSubscriberGroupInfo;
+import android.telephony.DomainSelectionService;
+import android.telephony.EmergencyRegResult;
 import android.telephony.LinkCapacityEstimate;
 import android.telephony.ModemInfo;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhoneCapability;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.PhysicalChannelConfig;
@@ -326,6 +351,12 @@
 import android.telephony.data.RouteSelectionDescriptor;
 import android.telephony.data.TrafficDescriptor;
 import android.telephony.data.UrspRule;
+import android.telephony.ims.RegistrationManager;
+import android.telephony.ims.feature.ConnectionFailureInfo;
+import android.telephony.ims.feature.MmTelFeature;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.telephony.ims.stub.ImsRegistrationImplBase.ImsDeregistrationReason;
+import android.telephony.satellite.SatelliteManager;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.SparseArray;
@@ -339,6 +370,7 @@
 import com.android.internal.telephony.cdma.sms.SmsEnvelope;
 import com.android.internal.telephony.data.KeepaliveStatus;
 import com.android.internal.telephony.data.KeepaliveStatus.KeepaliveStatusCode;
+import com.android.internal.telephony.imsphone.ImsCallInfo;
 import com.android.internal.telephony.uicc.AdnCapacity;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus;
 import com.android.internal.telephony.uicc.IccCardStatus;
@@ -346,6 +378,7 @@
 import com.android.internal.telephony.uicc.IccSlotPortMapping;
 import com.android.internal.telephony.uicc.IccSlotStatus;
 import com.android.internal.telephony.uicc.IccUtils;
+import com.android.internal.telephony.uicc.PortUtils;
 import com.android.internal.telephony.uicc.SimPhonebookRecord;
 import com.android.telephony.Rlog;
 
@@ -1441,8 +1474,9 @@
                     if (ComprehensionTlvTag.TEXT_STRING.value() == ctlv.getTag()) {
                         byte[] target = Arrays.copyOfRange(ctlv.getRawValue(), from,
                                 ctlv.getValueIndex() + ctlv.getLength());
-                        terminalResponse = terminalResponse.toLowerCase().replace(
-                                IccUtils.bytesToHexString(target).toLowerCase(), "********");
+                        terminalResponse = terminalResponse.toLowerCase(Locale.ROOT).replace(
+                                IccUtils.bytesToHexString(target).toLowerCase(Locale.ROOT),
+                                "********");
                     }
                     // The text string tag and the length field should also be hidden.
                     from = ctlv.getValueIndex() + ctlv.getLength();
@@ -1660,12 +1694,10 @@
         if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_IWLAN) != 0) {
             raf |= android.hardware.radio.RadioAccessFamily.IWLAN;
         }
-        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_LTE) != 0) {
+        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_LTE) != 0
+                || (networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_LTE_CA) != 0) {
             raf |= android.hardware.radio.RadioAccessFamily.LTE;
         }
-        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_LTE_CA) != 0) {
-            raf |= android.hardware.radio.RadioAccessFamily.LTE_CA;
-        }
         if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_NR) != 0) {
             raf |= android.hardware.radio.RadioAccessFamily.NR;
         }
@@ -1801,10 +1833,12 @@
      * @param p2 p2
      * @param p3 p3
      * @param data data
+     * @param radioHalVersion radio hal version
      * @return The converted SimApdu
      */
     public static android.hardware.radio.sim.SimApdu convertToHalSimApduAidl(int channel, int cla,
-            int instruction, int p1, int p2, int p3, String data) {
+            int instruction, int p1, int p2, int p3, String data, boolean isEs10Command,
+            HalVersion radioHalVersion) {
         android.hardware.radio.sim.SimApdu msg = new android.hardware.radio.sim.SimApdu();
         msg.sessionId = channel;
         msg.cla = cla;
@@ -1813,6 +1847,9 @@
         msg.p2 = p2;
         msg.p3 = p3;
         msg.data = convertNullToEmptyString(data);
+        if (radioHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_2_1)) {
+            msg.isEs10 = isEs10Command;
+        }
         return msg;
     }
 
@@ -3256,7 +3293,8 @@
             return new CellSignalStrengthNr(CellSignalStrengthNr.flip(ss.base.csiRsrp),
                     CellSignalStrengthNr.flip(ss.base.csiRsrq), ss.base.csiSinr,
                     ss.csiCqiTableIndex, ss.csiCqiReport, CellSignalStrengthNr.flip(ss.base.ssRsrp),
-                    CellSignalStrengthNr.flip(ss.base.ssRsrq), ss.base.ssSinr);
+                    CellSignalStrengthNr.flip(ss.base.ssRsrq), ss.base.ssSinr,
+                    CellInfo.UNAVAILABLE);
         }
         return null;
     }
@@ -3271,7 +3309,7 @@
         return new CellSignalStrengthNr(CellSignalStrengthNr.flip(ss.csiRsrp),
                 CellSignalStrengthNr.flip(ss.csiRsrq), ss.csiSinr, ss.csiCqiTableIndex,
                 primitiveArrayToArrayList(ss.csiCqiReport), CellSignalStrengthNr.flip(ss.ssRsrp),
-                CellSignalStrengthNr.flip(ss.ssRsrq), ss.ssSinr);
+                CellSignalStrengthNr.flip(ss.ssRsrq), ss.ssSinr, ss.timingAdvance);
     }
 
     private static ClosedSubscriberGroupInfo convertHalClosedSubscriberGroupInfo(
@@ -3835,9 +3873,13 @@
                         convertHalQosBandwidth(eps.uplink), eps.qci);
             case android.hardware.radio.data.Qos.nr:
                 android.hardware.radio.data.NrQos nr = qos.getNr();
+                int averagingWindowMs = nr.averagingWindowMillis;
+                if (averagingWindowMs
+                        == android.hardware.radio.data.NrQos.AVERAGING_WINDOW_UNKNOWN) {
+                    averagingWindowMs = nr.averagingWindowMs;
+                }
                 return new NrQos(convertHalQosBandwidth(nr.downlink),
-                        convertHalQosBandwidth(nr.uplink), nr.qfi, nr.fiveQi,
-                        nr.averagingWindowMs);
+                        convertHalQosBandwidth(nr.uplink), nr.qfi, nr.fiveQi, averagingWindowMs);
             default:
                 return null;
         }
@@ -4331,6 +4373,7 @@
             android.hardware.radio.sim.CardStatus cardStatus) {
         IccCardStatus iccCardStatus = new IccCardStatus();
         iccCardStatus.setCardState(cardStatus.cardState);
+        iccCardStatus.setMultipleEnabledProfilesMode(cardStatus.supportedMepMode);
         iccCardStatus.setUniversalPinState(cardStatus.universalPinState);
         iccCardStatus.mGsmUmtsSubscriptionAppIndex = cardStatus.gsmUmtsSubscriptionAppIndex;
         iccCardStatus.mCdmaSubscriptionAppIndex = cardStatus.cdmaSubscriptionAppIndex;
@@ -4358,7 +4401,9 @@
         }
         IccSlotPortMapping slotPortMapping = new IccSlotPortMapping();
         slotPortMapping.mPhysicalSlotIndex = cardStatus.slotMap.physicalSlotId;
-        slotPortMapping.mPortIndex = cardStatus.slotMap.portId;
+        slotPortMapping.mPortIndex = PortUtils.convertFromHalPortIndex(
+                cardStatus.slotMap.physicalSlotId, cardStatus.slotMap.portId,
+                iccCardStatus.mCardState, iccCardStatus.mSupportedMepMode);
         iccCardStatus.mSlotPortMapping = slotPortMapping;
         return iccCardStatus;
     }
@@ -4474,6 +4519,7 @@
                 }
                 iccSlotStatus.atr = slotStatus.atr;
                 iccSlotStatus.eid = slotStatus.eid;
+                iccSlotStatus.setMultipleEnabledProfilesMode(slotStatus.supportedMepMode);
                 response.add(iccSlotStatus);
             }
             return response;
@@ -4541,7 +4587,8 @@
             int logicalSlotIdx = mapping.getLogicalSlotIndex();
             res[logicalSlotIdx] = new android.hardware.radio.config.SlotPortMapping();
             res[logicalSlotIdx].physicalSlotId = mapping.getPhysicalSlotIndex();
-            res[logicalSlotIdx].portId = mapping.getPortIndex();
+            res[logicalSlotIdx].portId = PortUtils.convertToHalPortIndex(
+                    mapping.getPhysicalSlotIndex(), mapping.getPortIndex());
         }
         return res;
     }
@@ -4566,12 +4613,14 @@
     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;
             validationBeforeSwitchSupported = phoneCapability.isInternetLingeringSupported;
             for (int modemId : phoneCapability.logicalModemIds) {
                 logicalModemList.add(new ModemInfo(modemId));
@@ -4580,16 +4629,249 @@
             final android.hardware.radio.config.V1_1.PhoneCapability phoneCapability =
                     (android.hardware.radio.config.V1_1.PhoneCapability) o;
             maxActiveData = phoneCapability.maxActiveData;
+            maxActiveInternetData = 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);
     }
 
+    /**
+     * Convert network scan type
+     * @param scanType The network scan type
+     * @return The converted EmergencyScanType
+     */
+    public static int convertEmergencyScanType(int scanType) {
+        switch (scanType) {
+            case DomainSelectionService.SCAN_TYPE_LIMITED_SERVICE:
+                return android.hardware.radio.network.EmergencyScanType.LIMITED_SERVICE;
+            case DomainSelectionService.SCAN_TYPE_FULL_SERVICE:
+                return android.hardware.radio.network.EmergencyScanType.FULL_SERVICE;
+            default:
+                return android.hardware.radio.network.EmergencyScanType.NO_PREFERENCE;
+        }
+    }
+
+    /**
+     * Convert to EmergencyNetworkScanTrigger
+     * @param accessNetwork The list of access network types
+     * @param scanType The network scan type
+     * @return The converted EmergencyNetworkScanTrigger
+     */
+    public static android.hardware.radio.network.EmergencyNetworkScanTrigger
+            convertEmergencyNetworkScanTrigger(@NonNull int[] accessNetwork, int scanType) {
+        int[] halAccessNetwork = new int[accessNetwork.length];
+        for (int i = 0; i < accessNetwork.length; i++) {
+            halAccessNetwork[i] = convertToHalAccessNetworkAidl(accessNetwork[i]);
+        }
+
+        android.hardware.radio.network.EmergencyNetworkScanTrigger scanRequest =
+                new android.hardware.radio.network.EmergencyNetworkScanTrigger();
+
+        scanRequest.accessNetwork = halAccessNetwork;
+        scanRequest.scanType = convertEmergencyScanType(scanType);
+        return scanRequest;
+    }
+
+    /**
+     * Convert EmergencyRegResult.aidl to EmergencyRegResult.
+     * @param halResult EmergencyRegResult.aidl in HAL.
+     * @return Converted EmergencyRegResult.
+     */
+    public static EmergencyRegResult convertHalEmergencyRegResult(
+            android.hardware.radio.network.EmergencyRegResult halResult) {
+        return new EmergencyRegResult(
+                halResult.accessNetwork,
+                convertHalRegState(halResult.regState),
+                halResult.emcDomain,
+                halResult.isVopsSupported,
+                halResult.isEmcBearerSupported,
+                halResult.nwProvidedEmc,
+                halResult.nwProvidedEmf,
+                halResult.mcc,
+                halResult.mnc,
+                getCountryCodeForMccMnc(halResult.mcc, halResult.mnc));
+    }
+
+    private static @NonNull String getCountryCodeForMccMnc(
+            @NonNull String mcc, @NonNull String mnc) {
+        if (TextUtils.isEmpty(mcc)) return "";
+        if (TextUtils.isEmpty(mnc)) mnc = "000";
+        String operatorNumeric = TextUtils.concat(mcc, mnc).toString();
+
+        MccTable.MccMnc mccMnc = MccTable.MccMnc.fromOperatorNumeric(operatorNumeric);
+        return MccTable.geoCountryCodeForMccMnc(mccMnc);
+    }
+
+    /**
+     * Convert RegResult.aidl to RegistrationState.
+     * @param halRegState RegResult in HAL.
+     * @return Converted RegistrationState.
+     */
+    public static @NetworkRegistrationInfo.RegistrationState int convertHalRegState(
+            int halRegState) {
+        switch (halRegState) {
+            case android.hardware.radio.network.RegState.NOT_REG_MT_NOT_SEARCHING_OP:
+            case android.hardware.radio.network.RegState.NOT_REG_MT_NOT_SEARCHING_OP_EM:
+                return NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING;
+            case android.hardware.radio.network.RegState.REG_HOME:
+                return NetworkRegistrationInfo.REGISTRATION_STATE_HOME;
+            case android.hardware.radio.network.RegState.NOT_REG_MT_SEARCHING_OP:
+            case android.hardware.radio.network.RegState.NOT_REG_MT_SEARCHING_OP_EM:
+                return NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_SEARCHING;
+            case android.hardware.radio.network.RegState.REG_DENIED:
+            case android.hardware.radio.network.RegState.REG_DENIED_EM:
+                return NetworkRegistrationInfo.REGISTRATION_STATE_DENIED;
+            case android.hardware.radio.network.RegState.UNKNOWN:
+            case android.hardware.radio.network.RegState.UNKNOWN_EM:
+                return NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN;
+            case android.hardware.radio.network.RegState.REG_ROAMING:
+                return NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING;
+            default:
+                return NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING;
+        }
+    }
+
+    /** Converts the array of network types to readable String array */
+    public static @NonNull String accessNetworkTypesToString(
+            @NonNull @AccessNetworkConstants.RadioAccessNetworkType int[] accessNetworkTypes) {
+        int length = accessNetworkTypes.length;
+        StringBuilder sb = new StringBuilder("{");
+        if (length > 0) {
+            sb.append(Arrays.stream(accessNetworkTypes)
+                    .mapToObj(RILUtils::accessNetworkTypeToString)
+                    .collect(Collectors.joining(",")));
+        }
+        sb.append("}");
+        return sb.toString();
+    }
+
+    private static @NonNull String accessNetworkTypeToString(
+            @AccessNetworkConstants.RadioAccessNetworkType int accessNetworkType) {
+        switch (accessNetworkType) {
+            case AccessNetworkConstants.AccessNetworkType.UNKNOWN: return "UNKNOWN";
+            case AccessNetworkConstants.AccessNetworkType.GERAN: return "GERAN";
+            case AccessNetworkConstants.AccessNetworkType.UTRAN: return "UTRAN";
+            case AccessNetworkConstants.AccessNetworkType.EUTRAN: return "EUTRAN";
+            case AccessNetworkConstants.AccessNetworkType.CDMA2000: return "CDMA2000";
+            case AccessNetworkConstants.AccessNetworkType.IWLAN: return "IWLAN";
+            case AccessNetworkConstants.AccessNetworkType.NGRAN: return "NGRAN";
+            default: return Integer.toString(accessNetworkType);
+        }
+    }
+
+    /** Converts scan type to readable String */
+    public static @NonNull String scanTypeToString(
+            @DomainSelectionService.EmergencyScanType int scanType) {
+        switch (scanType) {
+            case DomainSelectionService.SCAN_TYPE_LIMITED_SERVICE:
+                return "LIMITED_SERVICE";
+            case DomainSelectionService.SCAN_TYPE_FULL_SERVICE:
+                return "FULL_SERVICE";
+            default:
+                return "NO_PREFERENCE";
+        }
+    }
+
+    /** Convert IMS deregistration reason */
+    public static @ImsDeregistrationReason int convertHalDeregistrationReason(int reason) {
+        switch (reason) {
+            case android.hardware.radio.ims.ImsDeregistrationReason.REASON_SIM_REMOVED:
+                return ImsRegistrationImplBase.REASON_SIM_REMOVED;
+            case android.hardware.radio.ims.ImsDeregistrationReason.REASON_SIM_REFRESH:
+                return ImsRegistrationImplBase.REASON_SIM_REFRESH;
+            case android.hardware.radio.ims.ImsDeregistrationReason
+                    .REASON_ALLOWED_NETWORK_TYPES_CHANGED:
+                return ImsRegistrationImplBase.REASON_ALLOWED_NETWORK_TYPES_CHANGED;
+            default:
+                return ImsRegistrationImplBase.REASON_UNKNOWN;
+        }
+    }
+
+    /**
+     * Convert the IMS traffic type.
+     * @param trafficType IMS traffic type like registration, voice, video, SMS, emergency, and etc.
+     * @return The converted IMS traffic type.
+     */
+    public static int convertImsTrafficType(@MmTelFeature.ImsTrafficType int trafficType) {
+        switch (trafficType) {
+            case MmTelFeature.IMS_TRAFFIC_TYPE_EMERGENCY:
+                return android.hardware.radio.ims.ImsTrafficType.EMERGENCY;
+            case MmTelFeature.IMS_TRAFFIC_TYPE_EMERGENCY_SMS:
+                return android.hardware.radio.ims.ImsTrafficType.EMERGENCY_SMS;
+            case MmTelFeature.IMS_TRAFFIC_TYPE_VOICE:
+                return android.hardware.radio.ims.ImsTrafficType.VOICE;
+            case MmTelFeature.IMS_TRAFFIC_TYPE_VIDEO:
+                return android.hardware.radio.ims.ImsTrafficType.VIDEO;
+            case MmTelFeature.IMS_TRAFFIC_TYPE_SMS:
+                return android.hardware.radio.ims.ImsTrafficType.SMS;
+            case MmTelFeature.IMS_TRAFFIC_TYPE_REGISTRATION:
+                return android.hardware.radio.ims.ImsTrafficType.REGISTRATION;
+        }
+        return android.hardware.radio.ims.ImsTrafficType.UT_XCAP;
+    }
+
+    /**
+     * Convert the IMS traffic direction.
+     * @param trafficDirection Indicates the traffic direction.
+     * @return The converted IMS traffic direction.
+     */
+    public static int convertImsTrafficDirection(
+            @MmTelFeature.ImsTrafficDirection int trafficDirection) {
+        switch (trafficDirection) {
+            case MmTelFeature.IMS_TRAFFIC_DIRECTION_INCOMING:
+                return android.hardware.radio.ims.ImsCall.Direction.INCOMING;
+            default:
+                return android.hardware.radio.ims.ImsCall.Direction.OUTGOING;
+        }
+    }
+
+    /**
+     * Convert the IMS connection failure reason.
+     * @param halReason  Specifies the reason that IMS connection failed.
+     * @return The converted IMS connection failure reason.
+     */
+    public static @ConnectionFailureInfo.FailureReason int convertHalConnectionFailureReason(
+            int halReason) {
+        switch (halReason) {
+            case android.hardware.radio.ims.ConnectionFailureInfo
+                    .ConnectionFailureReason.REASON_ACCESS_DENIED:
+                return ConnectionFailureInfo.REASON_ACCESS_DENIED;
+            case android.hardware.radio.ims.ConnectionFailureInfo
+                    .ConnectionFailureReason.REASON_NAS_FAILURE:
+                return ConnectionFailureInfo.REASON_NAS_FAILURE;
+            case android.hardware.radio.ims.ConnectionFailureInfo
+                    .ConnectionFailureReason.REASON_RACH_FAILURE:
+                return ConnectionFailureInfo.REASON_RACH_FAILURE;
+            case android.hardware.radio.ims.ConnectionFailureInfo
+                    .ConnectionFailureReason.REASON_RLC_FAILURE:
+                return ConnectionFailureInfo.REASON_RLC_FAILURE;
+            case android.hardware.radio.ims.ConnectionFailureInfo
+                    .ConnectionFailureReason.REASON_RRC_REJECT:
+                return ConnectionFailureInfo.REASON_RRC_REJECT;
+            case android.hardware.radio.ims.ConnectionFailureInfo
+                    .ConnectionFailureReason.REASON_RRC_TIMEOUT:
+                return ConnectionFailureInfo.REASON_RRC_TIMEOUT;
+            case android.hardware.radio.ims.ConnectionFailureInfo
+                    .ConnectionFailureReason.REASON_NO_SERVICE:
+                return ConnectionFailureInfo.REASON_NO_SERVICE;
+            case android.hardware.radio.ims.ConnectionFailureInfo
+                    .ConnectionFailureReason.REASON_PDN_NOT_AVAILABLE:
+                return ConnectionFailureInfo.REASON_PDN_NOT_AVAILABLE;
+            case android.hardware.radio.ims.ConnectionFailureInfo
+                    .ConnectionFailureReason.REASON_RF_BUSY:
+                return ConnectionFailureInfo.REASON_RF_BUSY;
+        }
+        return ConnectionFailureInfo.REASON_UNSPECIFIED;
+    }
+
     /** Append the data to the end of an ArrayList */
     public static void appendPrimitiveArrayToArrayList(byte[] src, ArrayList<Byte> dst) {
         for (byte b : src) {
@@ -4983,6 +5265,9 @@
                 return "GET_SIM_PHONEBOOK_RECORDS";
             case RIL_REQUEST_UPDATE_SIM_PHONEBOOK_RECORD:
                 return "UPDATE_SIM_PHONEBOOK_RECORD";
+            case RIL_REQUEST_DEVICE_IMEI:
+                return "DEVICE_IMEI";
+            /* The following requests are not defined in RIL.h */
             case RIL_REQUEST_GET_SLOT_STATUS:
                 return "GET_SLOT_STATUS";
             case RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING:
@@ -5041,6 +5326,36 @@
                 return "SET_USAGE_SETTING";
             case RIL_REQUEST_GET_USAGE_SETTING:
                 return "GET_USAGE_SETTING";
+            case RIL_REQUEST_SET_EMERGENCY_MODE:
+                return "SET_EMERGENCY_MODE";
+            case RIL_REQUEST_TRIGGER_EMERGENCY_NETWORK_SCAN:
+                return "TRIGGER_EMERGENCY_NETWORK_SCAN";
+            case RIL_REQUEST_CANCEL_EMERGENCY_NETWORK_SCAN:
+                return "CANCEL_EMERGENCY_NETWORK_SCAN";
+            case RIL_REQUEST_EXIT_EMERGENCY_MODE:
+                return "EXIT_EMERGENCY_MODE";
+            case RIL_REQUEST_SET_SRVCC_CALL_INFO:
+                return "SET_SRVCC_CALL_INFO";
+            case RIL_REQUEST_UPDATE_IMS_REGISTRATION_INFO:
+                return "UPDATE_IMS_REGISTRATION_INFO";
+            case RIL_REQUEST_START_IMS_TRAFFIC:
+                return "START_IMS_TRAFFIC";
+            case RIL_REQUEST_STOP_IMS_TRAFFIC:
+                return "STOP_IMS_TRAFFIC";
+            case RIL_REQUEST_SEND_ANBR_QUERY:
+                return "SEND_ANBR_QUERY";
+            case RIL_REQUEST_TRIGGER_EPS_FALLBACK:
+                return "TRIGGER_EPS_FALLBACK";
+            case RIL_REQUEST_SET_NULL_CIPHER_AND_INTEGRITY_ENABLED:
+                return "SET_NULL_CIPHER_AND_INTEGRITY_ENABLED";
+            case RIL_REQUEST_IS_NULL_CIPHER_AND_INTEGRITY_ENABLED:
+                return "IS_NULL_CIPHER_AND_INTEGRITY_ENABLED";
+            case RIL_REQUEST_UPDATE_IMS_CALL_STATUS:
+                return "UPDATE_IMS_CALL_STATUS";
+            case RIL_REQUEST_SET_N1_MODE_ENABLED:
+                return "SET_N1_MODE_ENABLED";
+            case RIL_REQUEST_IS_N1_MODE_ENABLED:
+                return "IS_N1_MODE_ENABLED";
             default:
                 return "<unknown request " + request + ">";
         }
@@ -5161,6 +5476,9 @@
                 return "UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED";
             case RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED:
                 return "UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED";
+            case RIL_UNSOL_SLICING_CONFIG_CHANGED:
+                return "UNSOL_SLICING_CONFIG_CHANGED";
+            /* The follow unsols are not defined in RIL.h */
             case RIL_UNSOL_ICC_SLOT_STATUS:
                 return "UNSOL_ICC_SLOT_STATUS";
             case RIL_UNSOL_PHYSICAL_CHANNEL_CONFIG:
@@ -5173,8 +5491,16 @@
                 return "UNSOL_REGISTRATION_FAILED";
             case RIL_UNSOL_BARRING_INFO_CHANGED:
                 return "UNSOL_BARRING_INFO_CHANGED";
+            case RIL_UNSOL_EMERGENCY_NETWORK_SCAN_RESULT:
+                return "UNSOL_EMERGENCY_NETWORK_SCAN_RESULT";
+            case RIL_UNSOL_TRIGGER_IMS_DEREGISTRATION:
+                return "UNSOL_TRIGGER_IMS_DEREGISTRATION";
+            case RIL_UNSOL_CONNECTION_SETUP_FAILURE:
+                return "UNSOL_CONNECTION_SETUP_FAILURE";
+            case RIL_UNSOL_NOTIFY_ANBR:
+                return "UNSOL_NOTIFY_ANBR";
             default:
-                return "<unknown response>";
+                return "<unknown response " + response + ">";
         }
     }
 
@@ -5321,6 +5647,302 @@
         return sb.toString();
     }
 
+    /**
+     * Converts the list of call information for Single Radio Voice Call Continuity(SRVCC).
+     *
+     * @param srvccConnections The list of call information for SRVCC.
+     * @return The converted list of call information.
+     */
+    public static android.hardware.radio.ims.SrvccCall[] convertToHalSrvccCall(
+            SrvccConnection[] srvccConnections) {
+        if (srvccConnections == null) {
+            return new android.hardware.radio.ims.SrvccCall[0];
+        }
+
+        int length = srvccConnections.length;
+        android.hardware.radio.ims.SrvccCall[] srvccCalls =
+                new android.hardware.radio.ims.SrvccCall[length];
+
+        for (int i = 0; i < length; i++) {
+            srvccCalls[i] = new android.hardware.radio.ims.SrvccCall();
+            srvccCalls[i].index = i + 1;
+            srvccCalls[i].callType = convertSrvccCallType(srvccConnections[i].getType());
+            srvccCalls[i].callState = convertCallState(srvccConnections[i].getState());
+            srvccCalls[i].callSubstate =
+                    convertSrvccCallSubState(srvccConnections[i].getSubState());
+            srvccCalls[i].ringbackToneType =
+                    convertSrvccCallRingbackToneType(srvccConnections[i].getRingbackToneType());
+            srvccCalls[i].isMpty = srvccConnections[i].isMultiParty();
+            srvccCalls[i].isMT = srvccConnections[i].isIncoming();
+            srvccCalls[i].number = TextUtils.emptyIfNull(srvccConnections[i].getNumber());
+            srvccCalls[i].numPresentation =
+                    convertPresentation(srvccConnections[i].getNumberPresentation());
+            srvccCalls[i].name = TextUtils.emptyIfNull(srvccConnections[i].getName());
+            srvccCalls[i].namePresentation =
+                    convertPresentation(srvccConnections[i].getNamePresentation());
+        }
+
+        return srvccCalls;
+    }
+
+    /**
+     * Converts the call type.
+     *
+     * @param type The call type.
+     * @return The converted call type.
+     */
+    public static int convertSrvccCallType(int type) {
+        switch (type) {
+            case  SrvccConnection.CALL_TYPE_NORMAL:
+                return android.hardware.radio.ims.SrvccCall.CallType.NORMAL;
+            case  SrvccConnection.CALL_TYPE_EMERGENCY:
+                return android.hardware.radio.ims.SrvccCall.CallType.EMERGENCY;
+            default:
+                throw new RuntimeException("illegal call type " + type);
+        }
+    }
+
+    /**
+     * Converts the call state.
+     *
+     * @param state The call state.
+     * @return The converted call state.
+     */
+    public static int convertCallState(Call.State state) {
+        switch (state) {
+            case ACTIVE: return android.hardware.radio.voice.Call.STATE_ACTIVE;
+            case HOLDING: return android.hardware.radio.voice.Call.STATE_HOLDING;
+            case DIALING: return android.hardware.radio.voice.Call.STATE_DIALING;
+            case ALERTING: return android.hardware.radio.voice.Call.STATE_ALERTING;
+            case INCOMING: return android.hardware.radio.voice.Call.STATE_INCOMING;
+            case WAITING: return android.hardware.radio.voice.Call.STATE_WAITING;
+            default:
+                throw new RuntimeException("illegal state " + state);
+        }
+    }
+
+    /**
+     * Converts the substate of a call.
+     *
+     * @param state The substate of a call.
+     * @return The converted substate.
+     */
+    public static int convertSrvccCallSubState(int state) {
+        switch (state) {
+            case SrvccConnection.SUBSTATE_NONE:
+                return android.hardware.radio.ims.SrvccCall.CallSubState.NONE;
+            case SrvccConnection.SUBSTATE_PREALERTING:
+                return android.hardware.radio.ims.SrvccCall.CallSubState.PREALERTING;
+            default:
+                throw new RuntimeException("illegal substate " + state);
+        }
+    }
+
+    /**
+     * Converts the ringback tone type.
+     *
+     * @param type The ringback tone type.
+     * @return The converted ringback tone type.
+     */
+    public static int convertSrvccCallRingbackToneType(int type) {
+        switch (type) {
+            case SrvccConnection.TONE_NONE:
+                return android.hardware.radio.ims.SrvccCall.ToneType.NONE;
+            case SrvccConnection.TONE_LOCAL:
+                return android.hardware.radio.ims.SrvccCall.ToneType.LOCAL;
+            case SrvccConnection.TONE_NETWORK:
+                return android.hardware.radio.ims.SrvccCall.ToneType.NETWORK;
+            default:
+                throw new RuntimeException("illegal ringback tone type " + type);
+        }
+    }
+
+    /**
+     * Converts the number presentation type for caller id display.
+     *
+     * @param presentation The number presentation type.
+     * @return The converted presentation type.
+     */
+    public static int convertPresentation(int presentation) {
+        switch (presentation) {
+            case PhoneConstants.PRESENTATION_ALLOWED:
+                return android.hardware.radio.voice.Call.PRESENTATION_ALLOWED;
+            case PhoneConstants.PRESENTATION_RESTRICTED:
+                return android.hardware.radio.voice.Call.PRESENTATION_RESTRICTED;
+            case PhoneConstants.PRESENTATION_UNKNOWN:
+                return android.hardware.radio.voice.Call.PRESENTATION_UNKNOWN;
+            case PhoneConstants.PRESENTATION_PAYPHONE:
+                return android.hardware.radio.voice.Call.PRESENTATION_PAYPHONE;
+            default:
+                throw new RuntimeException("illegal presentation " + presentation);
+        }
+    }
+
+    /**
+     * Converts IMS registration state.
+     *
+     * @param state The IMS registration state.
+     * @return The converted HAL IMS registration state.
+     */
+    public static int convertImsRegistrationState(int state) {
+        switch (state) {
+            case RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED:
+                return android.hardware.radio.ims.ImsRegistrationState.NOT_REGISTERED;
+            case RegistrationManager.REGISTRATION_STATE_REGISTERED:
+                return android.hardware.radio.ims.ImsRegistrationState.REGISTERED;
+            default:
+                throw new RuntimeException("illegal state " + state);
+        }
+    }
+
+    /**
+     * Converts IMS service radio technology.
+     *
+     * @param imsRadioTech The IMS service radio technology.
+     * @return The converted HAL access network type.
+     */
+
+    public static int convertImsRegistrationTech(
+            @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech) {
+        switch (imsRadioTech) {
+            case ImsRegistrationImplBase.REGISTRATION_TECH_LTE:
+                return android.hardware.radio.AccessNetwork.EUTRAN;
+            case ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN:
+                return android.hardware.radio.AccessNetwork.IWLAN;
+            case ImsRegistrationImplBase.REGISTRATION_TECH_NR:
+                return android.hardware.radio.AccessNetwork.NGRAN;
+            case ImsRegistrationImplBase.REGISTRATION_TECH_3G:
+                return android.hardware.radio.AccessNetwork.UTRAN;
+            default:
+                return android.hardware.radio.AccessNetwork.UNKNOWN;
+        }
+    }
+
+    /**
+     * Converts IMS capabilities.
+     *
+     * @param capabilities The IMS capabilities.
+     * @return The converted HAL IMS capabilities.
+     */
+    public static int convertImsCapability(int capabilities) {
+        int halCapabilities = android.hardware.radio.ims.ImsRegistration.IMS_MMTEL_CAPABILITY_NONE;
+        if ((capabilities & CommandsInterface.IMS_MMTEL_CAPABILITY_VOICE) > 0) {
+            halCapabilities |=
+                    android.hardware.radio.ims.ImsRegistration.IMS_MMTEL_CAPABILITY_VOICE;
+        }
+        if ((capabilities & CommandsInterface.IMS_MMTEL_CAPABILITY_VIDEO) > 0) {
+            halCapabilities |=
+                    android.hardware.radio.ims.ImsRegistration.IMS_MMTEL_CAPABILITY_VIDEO;
+        }
+        if ((capabilities & CommandsInterface.IMS_MMTEL_CAPABILITY_SMS) > 0) {
+            halCapabilities |= android.hardware.radio.ims.ImsRegistration.IMS_MMTEL_CAPABILITY_SMS;
+        }
+        if ((capabilities & CommandsInterface.IMS_RCS_CAPABILITIES) > 0) {
+            halCapabilities |= android.hardware.radio.ims.ImsRegistration.IMS_RCS_CAPABILITIES;
+        }
+        return halCapabilities;
+    }
+
+    /** Converts the ImsCallInfo instances to HAL ImsCall instances. */
+    public static android.hardware.radio.ims.ImsCall[] convertImsCallInfo(
+            List<ImsCallInfo> imsCallInfos) {
+        if (imsCallInfos == null) {
+            return new android.hardware.radio.ims.ImsCall[0];
+        }
+
+        int length = 0;
+        for (int i = 0; i < imsCallInfos.size(); i++) {
+            if (imsCallInfos.get(i) != null) length++;
+        }
+        if (length == 0) {
+            return new android.hardware.radio.ims.ImsCall[0];
+        }
+
+        android.hardware.radio.ims.ImsCall[] halInfos =
+                new android.hardware.radio.ims.ImsCall[length];
+
+        int index = 0;
+        for (int i = 0; i < imsCallInfos.size(); i++) {
+            ImsCallInfo info = imsCallInfos.get(i);
+            if (info == null) continue;
+
+            halInfos[index] = new android.hardware.radio.ims.ImsCall();
+            halInfos[index].index = info.getIndex();
+            halInfos[index].callState = convertToHalImsCallState(info.getCallState());
+            halInfos[index].callType = info.isEmergencyCall()
+                    ? android.hardware.radio.ims.ImsCall.CallType.EMERGENCY
+                    : android.hardware.radio.ims.ImsCall.CallType.NORMAL;
+            halInfos[index].accessNetwork = convertToHalAccessNetworkAidl(info.getCallRadioTech());
+            halInfos[index].direction = info.isIncoming()
+                    ? android.hardware.radio.ims.ImsCall.Direction.INCOMING
+                    : android.hardware.radio.ims.ImsCall.Direction.OUTGOING;
+            halInfos[index].isHeldByRemote = info.isHeldByRemote();
+            index++;
+        }
+
+        return halInfos;
+    }
+
+    /**
+     * Convert satellite-related errors from CommandException.Error to
+     * SatelliteManager.SatelliteServiceResult.
+     * @param error The satellite error.
+     * @return The converted SatelliteServiceResult.
+     */
+    @SatelliteManager.SatelliteError
+    public static int convertToSatelliteError(
+            CommandException.Error error) {
+        switch (error) {
+            case INTERNAL_ERR:
+                //fallthrough to SYSTEM_ERR
+            case MODEM_ERR:
+                //fallthrough to SYSTEM_ERR
+            case SYSTEM_ERR:
+                return SatelliteManager.SATELLITE_MODEM_ERROR;
+            case INVALID_ARGUMENTS:
+                return SatelliteManager.SATELLITE_INVALID_ARGUMENTS;
+            case INVALID_MODEM_STATE:
+                return SatelliteManager.SATELLITE_INVALID_MODEM_STATE;
+            case RADIO_NOT_AVAILABLE:
+                return SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE;
+            case REQUEST_NOT_SUPPORTED:
+                return SatelliteManager.SATELLITE_REQUEST_NOT_SUPPORTED;
+            case NO_MEMORY:
+                //fallthrough to NO_RESOURCES
+            case NO_RESOURCES:
+                return SatelliteManager.SATELLITE_NO_RESOURCES;
+            case NETWORK_ERR:
+                return SatelliteManager.SATELLITE_NETWORK_ERROR;
+            case NO_NETWORK_FOUND:
+                return SatelliteManager.SATELLITE_NOT_REACHABLE;
+            case ABORTED:
+                return SatelliteManager.SATELLITE_REQUEST_ABORTED;
+            case ACCESS_BARRED:
+                return SatelliteManager.SATELLITE_ACCESS_BARRED;
+            default:
+                return SatelliteManager.SATELLITE_ERROR;
+        }
+    }
+
+    /**
+     * Converts the call state to HAL IMS call state.
+     *
+     * @param state The {@link Call.State}.
+     * @return The converted {@link android.hardware.radio.ims.ImsCall.CallState}.
+     */
+    private static int convertToHalImsCallState(Call.State state) {
+        switch (state) {
+            case ACTIVE: return android.hardware.radio.ims.ImsCall.CallState.ACTIVE;
+            case HOLDING: return android.hardware.radio.ims.ImsCall.CallState.HOLDING;
+            case DIALING: return android.hardware.radio.ims.ImsCall.CallState.DIALING;
+            case ALERTING: return android.hardware.radio.ims.ImsCall.CallState.ALERTING;
+            case INCOMING: return android.hardware.radio.ims.ImsCall.CallState.INCOMING;
+            case WAITING: return android.hardware.radio.ims.ImsCall.CallState.WAITING;
+            case DISCONNECTING: return android.hardware.radio.ims.ImsCall.CallState.DISCONNECTING;
+            default: return android.hardware.radio.ims.ImsCall.CallState.DISCONNECTED;
+        }
+    }
+
     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 e455ecb..3e2be1d 100644
--- a/src/java/com/android/internal/telephony/RadioConfig.java
+++ b/src/java/com/android/internal/telephony/RadioConfig.java
@@ -358,7 +358,7 @@
 
             if (rr != null) {
                 Trace.asyncTraceForTrackEnd(
-                        Trace.TRACE_TAG_NETWORK, "RIL", "" /* unused */, rr.mSerial);
+                        Trace.TRACE_TAG_NETWORK, "RIL", rr.mSerial);
                 mRequestList.remove(serial);
             }
         }
@@ -642,4 +642,9 @@
     private static void loge(String log) {
         Rlog.e(TAG, log);
     }
+
+    @Override
+    public String toString() {
+        return "RadioConfig[" + "mRadioConfigProxy=" + mRadioConfigProxy + ']';
+    }
 }
diff --git a/src/java/com/android/internal/telephony/RadioConfigProxy.java b/src/java/com/android/internal/telephony/RadioConfigProxy.java
index a8601f1..edeb558 100644
--- a/src/java/com/android/internal/telephony/RadioConfigProxy.java
+++ b/src/java/com/android/internal/telephony/RadioConfigProxy.java
@@ -336,4 +336,11 @@
                     mRadioConfig.obtainMessage(RadioConfig.EVENT_AIDL_SERVICE_DEAD));
         }
     }
+
+    @Override
+    public String toString() {
+        return "RadioConfigProxy["
+                + "mRadioHalVersion=" + mRadioHalVersion
+                + ", mRadioConfigHalVersion=" + mRadioConfigHalVersion + ']';
+    }
 }
diff --git a/src/java/com/android/internal/telephony/RadioDataProxy.java b/src/java/com/android/internal/telephony/RadioDataProxy.java
index cbc762a..9671077 100644
--- a/src/java/com/android/internal/telephony/RadioDataProxy.java
+++ b/src/java/com/android/internal/telephony/RadioDataProxy.java
@@ -45,12 +45,22 @@
      * Set IRadioData as the AIDL implementation for RadioServiceProxy
      * @param halVersion Radio HAL version
      * @param data IRadioData implementation
+     *
+     * @return updated HAL version
      */
-    public void setAidl(HalVersion halVersion, android.hardware.radio.data.IRadioData data) {
-        mHalVersion = halVersion;
+    public HalVersion setAidl(HalVersion halVersion, android.hardware.radio.data.IRadioData data) {
+        HalVersion version = halVersion;
+        try {
+            version = RIL.getServiceHalVersion(data.getInterfaceVersion());
+        } catch (RemoteException e) {
+            Rlog.e(TAG, "setAidl: " + e);
+        }
+        mHalVersion = version;
         mDataProxy = data;
         mIsAidl = true;
-        Rlog.d(TAG, "AIDL initialized");
+
+        Rlog.d(TAG, "AIDL initialized mHalVersion=" + mHalVersion);
+        return mHalVersion;
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/RadioImsProxy.java b/src/java/com/android/internal/telephony/RadioImsProxy.java
new file mode 100644
index 0000000..cde2e06
--- /dev/null
+++ b/src/java/com/android/internal/telephony/RadioImsProxy.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2022 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.os.RemoteException;
+import android.telephony.Rlog;
+
+/**
+ * A holder for IRadioIms.
+ * Use getAidl to get IRadioIms and call the AIDL implementations of the HAL APIs.
+ */
+public class RadioImsProxy extends RadioServiceProxy {
+    private static final String TAG = "RadioImsProxy";
+    private volatile android.hardware.radio.ims.IRadioIms mImsProxy = null;
+
+    /**
+     * Sets IRadioIms as the AIDL implementation for RadioServiceProxy.
+     * @param halVersion Radio HAL version.
+     * @param ims IRadioIms implementation.
+     *
+     * @return updated HAL version.
+     */
+    public HalVersion setAidl(HalVersion halVersion, android.hardware.radio.ims.IRadioIms ims) {
+        HalVersion version = halVersion;
+        try {
+            version = RIL.getServiceHalVersion(ims.getInterfaceVersion());
+        } catch (RemoteException e) {
+            Rlog.e(TAG, "setAidl: " + e);
+        }
+        mHalVersion = version;
+        mImsProxy = ims;
+        mIsAidl = true;
+
+        Rlog.d(TAG, "AIDL initialized mHalVersion=" + mHalVersion);
+        return mHalVersion;
+    }
+
+    /**
+     * Gets the AIDL implementation of RadioImsProxy.
+     * @return IRadioIms implementation.
+     */
+    public android.hardware.radio.ims.IRadioIms getAidl() {
+        return mImsProxy;
+    }
+
+    /**
+     * Resets RadioImsProxy.
+     */
+    @Override
+    public void clear() {
+        super.clear();
+        mImsProxy = null;
+    }
+
+    /**
+     * Checks whether a RadioIms implementation exists.
+     * @return true if there is neither a HIDL nor AIDL implementation.
+     */
+    @Override
+    public boolean isEmpty() {
+        return mRadioProxy == null && mImsProxy == null;
+    }
+
+    /**
+     * No implementation in IRadioIms.
+     * @throws RemoteException.
+     */
+    @Override
+    public void responseAcknowledgement() throws RemoteException {
+        /* Currently, IRadioIms doesn't support the following response types:
+         * - RadioIndicationType.UNSOLICITED_ACK_EXP
+         * - RadioResponseType.SOLICITED_ACK_EXP */
+        // no-op
+    }
+
+    /**
+     * Calls IRadioIms#setSrvccCallInfo.
+     * @param serial Serial number of request.
+     * @param srvccCalls The list of call information.
+     * @throws RemoteException.
+     */
+    public void setSrvccCallInfo(int serial,
+            android.hardware.radio.ims.SrvccCall[] srvccCalls) throws RemoteException {
+        if (isEmpty()) return;
+        if (isAidl()) {
+            mImsProxy.setSrvccCallInfo(serial, srvccCalls);
+        }
+    }
+
+    /**
+     * Calls IRadioIms#updateImsRegistrationInfo.
+     * @param serial Serial number of request.
+     * @param registrationInfo The registration state information.
+     * @throws RemoteException.
+     */
+    public void updateImsRegistrationInfo(int serial,
+            android.hardware.radio.ims.ImsRegistration registrationInfo) throws RemoteException {
+        if (isEmpty()) return;
+        if (isAidl()) {
+            mImsProxy.updateImsRegistrationInfo(serial, registrationInfo);
+        }
+    }
+
+    /**
+     * Calls IRadioIms#startImsTraffic.
+     * @param serial Serial number of request.
+     * @param token A nonce to identify the request.
+     * @param trafficType IMS traffic type like registration, voice, video, SMS, emergency, and etc.
+     * @param accessNetworkType The type of underlying radio access network used.
+     * @param trafficDirection Indicates whether traffic is originated by mobile originated or
+     *        mobile terminated use case eg. MO/MT call/SMS etc.
+     * @throws RemoteException.
+     */
+    public void startImsTraffic(int serial, int token, int trafficType, int accessNetworkType,
+            int trafficDirection) throws RemoteException {
+        if (isEmpty()) return;
+        if (isAidl()) {
+            mImsProxy.startImsTraffic(serial,
+                    token, trafficType, accessNetworkType, trafficDirection);
+        }
+    }
+
+    /**
+     * Calls IRadioIms#stopImsTraffic.
+     * @param serial Serial number of request.
+     * @param token The token assigned by startImsTraffic.
+     * @throws RemoteException.
+     */
+    public void stopImsTraffic(int serial, int token)
+            throws RemoteException {
+        if (isEmpty()) return;
+        if (isAidl()) {
+            mImsProxy.stopImsTraffic(serial, token);
+        }
+    }
+
+    /**
+     * Calls IRadioIms#triggerEpsFallback.
+     * @param serial Serial number of request.
+     * @param reason Specifies the reason for EPS fallback.
+     * @throws RemoteException.
+     */
+    public void triggerEpsFallback(int serial, int reason)
+            throws RemoteException {
+        if (isEmpty()) return;
+        if (isAidl()) {
+            mImsProxy.triggerEpsFallback(serial, reason);
+        }
+    }
+
+    /**
+     * Calls IRadioIms#sendAnbrQuery.
+     * @param serial Serial number of request.
+     * @param mediaType Media type is used to identify media stream such as audio or video.
+     * @param direction Direction of this packet stream (e.g. uplink or downlink).
+     * @param bitsPerSecond The bit rate requested by the opponent UE.
+     * @throws RemoteException.
+     */
+    public void sendAnbrQuery(int serial, int mediaType, int direction, int bitsPerSecond)
+            throws RemoteException {
+        if (isEmpty()) return;
+        if (isAidl()) {
+            mImsProxy.sendAnbrQuery(serial, mediaType, direction, bitsPerSecond);
+        }
+    }
+
+    /**
+     * Call IRadioIms#updateImsCallStatus
+     * @param serial Serial number of request.
+     * @param imsCalls The list of call status information.
+     * @throws RemoteException.
+     */
+    public void updateImsCallStatus(int serial,
+            android.hardware.radio.ims.ImsCall[] imsCalls) throws RemoteException {
+        if (isEmpty()) return;
+        if (isAidl()) {
+            mImsProxy.updateImsCallStatus(serial, imsCalls);
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/RadioIndication.java b/src/java/com/android/internal/telephony/RadioIndication.java
index d947d53..4f75412 100644
--- a/src/java/com/android/internal/telephony/RadioIndication.java
+++ b/src/java/com/android/internal/telephony/RadioIndication.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.telephony;
 
-import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID;
+import static android.telephony.TelephonyManager.HAL_SERVICE_RADIO;
 
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CALL_RING;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION;
@@ -136,7 +136,7 @@
      * @param radioState android.hardware.radio.V1_0.RadioState
      */
     public void radioStateChanged(int indicationType, int radioState) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         int state = RILUtils.convertHalRadioState(radioState);
         if (mRil.isLogOrTrace()) {
@@ -148,7 +148,7 @@
     }
 
     public void callStateChanged(int indicationType) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED);
 
@@ -160,7 +160,7 @@
      * @param indicationType RadioIndicationType
      */
     public void networkStateChanged(int indicationType) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_NETWORK_STATE_CHANGED);
 
@@ -168,7 +168,7 @@
     }
 
     public void newSms(int indicationType, ArrayList<Byte> pdu) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         byte[] pduArray = RILUtils.arrayListToPrimitiveArray(pdu);
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_NEW_SMS);
@@ -181,7 +181,7 @@
     }
 
     public void newSmsStatusReport(int indicationType, ArrayList<Byte> pdu) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         byte[] pduArray = RILUtils.arrayListToPrimitiveArray(pdu);
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT);
@@ -192,7 +192,7 @@
     }
 
     public void newSmsOnSim(int indicationType, int recordNumber) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_NEW_SMS_ON_SIM);
 
@@ -202,7 +202,7 @@
     }
 
     public void onUssd(int indicationType, int ussdModeType, String msg) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLogMore(RIL_UNSOL_ON_USSD, "" + ussdModeType);
 
@@ -216,7 +216,7 @@
     }
 
     public void nitzTimeReceived(int indicationType, String nitzTime, long receivedTime) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_NITZ_TIME_RECEIVED, nitzTime);
 
@@ -241,7 +241,7 @@
 
     public void currentSignalStrength(int indicationType,
                                       android.hardware.radio.V1_0.SignalStrength signalStrength) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         SignalStrength ssInitial = RILUtils.convertHalSignalStrength(signalStrength);
 
@@ -259,7 +259,7 @@
      */
     public void currentLinkCapacityEstimate(int indicationType,
                                             android.hardware.radio.V1_2.LinkCapacityEstimate lce) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         List<LinkCapacityEstimate> response = RILUtils.convertHalLceData(lce);
 
@@ -275,7 +275,7 @@
      */
     public void currentLinkCapacityEstimate_1_6(int indicationType,
             android.hardware.radio.V1_6.LinkCapacityEstimate lce) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         List<LinkCapacityEstimate> response = RILUtils.convertHalLceData(lce);
 
@@ -291,7 +291,7 @@
      */
     public void currentSignalStrength_1_2(int indicationType,
                                       android.hardware.radio.V1_2.SignalStrength signalStrength) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         SignalStrength ss = RILUtils.convertHalSignalStrength(signalStrength);
         // Note this is set to "verbose" because it happens frequently
@@ -308,7 +308,7 @@
     public void currentSignalStrength_1_4(int indicationType,
             android.hardware.radio.V1_4.SignalStrength signalStrength) {
 
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         SignalStrength ss = RILUtils.convertHalSignalStrength(signalStrength);
 
@@ -325,7 +325,7 @@
     public void currentSignalStrength_1_6(int indicationType,
             android.hardware.radio.V1_6.SignalStrength signalStrength) {
 
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         SignalStrength ss = RILUtils.convertHalSignalStrength(signalStrength);
 
@@ -341,7 +341,7 @@
      */
     public void currentPhysicalChannelConfigs_1_4(int indicationType,
             ArrayList<android.hardware.radio.V1_4.PhysicalChannelConfig> configs) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
         physicalChannelConfigsIndication(configs);
     }
 
@@ -350,7 +350,7 @@
      */
     public void currentPhysicalChannelConfigs_1_6(int indicationType,
             ArrayList<android.hardware.radio.V1_6.PhysicalChannelConfig> configs) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
         physicalChannelConfigsIndication(configs);
     }
 
@@ -359,7 +359,7 @@
      */
     public void currentPhysicalChannelConfigs(int indicationType,
             ArrayList<android.hardware.radio.V1_2.PhysicalChannelConfig> configs) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
         physicalChannelConfigsIndication(configs);
     }
 
@@ -368,7 +368,7 @@
      */
     public void currentEmergencyNumberList(int indicationType,
             ArrayList<android.hardware.radio.V1_4.EmergencyNumber> emergencyNumberList) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
         List<EmergencyNumber> response = new ArrayList<>(emergencyNumberList.size());
 
         for (android.hardware.radio.V1_4.EmergencyNumber emergencyNumberHal
@@ -422,7 +422,7 @@
     }
 
     public void suppSvcNotify(int indicationType, SuppSvcNotification suppSvcNotification) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         SuppServiceNotification notification = new SuppServiceNotification();
         notification.notificationType = suppSvcNotification.isMT ? 1 : 0;
@@ -441,7 +441,7 @@
     }
 
     public void stkSessionEnd(int indicationType) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_STK_SESSION_END);
 
@@ -451,7 +451,7 @@
     }
 
     public void stkProactiveCommand(int indicationType, String cmd) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_STK_PROACTIVE_COMMAND);
 
@@ -461,7 +461,7 @@
     }
 
     public void stkEventNotify(int indicationType, String cmd) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_STK_EVENT_NOTIFY);
 
@@ -471,7 +471,7 @@
     }
 
     public void stkCallSetup(int indicationType, long timeout) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_STK_CALL_SETUP, timeout);
 
@@ -481,7 +481,7 @@
     }
 
     public void simSmsStorageFull(int indicationType) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_SIM_SMS_STORAGE_FULL);
 
@@ -491,7 +491,7 @@
     }
 
     public void simRefresh(int indicationType, SimRefreshResult refreshResult) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         IccRefreshResponse response = new IccRefreshResponse();
         response.refreshResult = refreshResult.type;
@@ -504,7 +504,7 @@
     }
 
     public void callRing(int indicationType, boolean isGsm, CdmaSignalInfoRecord record) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         char response[] = null;
 
@@ -527,7 +527,7 @@
     }
 
     public void simStatusChanged(int indicationType) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED);
 
@@ -535,7 +535,7 @@
     }
 
     public void cdmaNewSms(int indicationType, CdmaSmsMessage msg) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_CDMA_NEW_SMS);
 
@@ -546,7 +546,7 @@
     }
 
     public void newBroadcastSms(int indicationType, ArrayList<Byte> data) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         byte[] response = RILUtils.arrayListToPrimitiveArray(data);
         if (mRil.isLogOrTrace()) {
@@ -560,7 +560,7 @@
     }
 
     public void cdmaRuimSmsStorageFull(int indicationType) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_CDMA_RUIM_SMS_STORAGE_FULL);
 
@@ -570,7 +570,7 @@
     }
 
     public void restrictedStateChanged(int indicationType, int state) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLogvRet(RIL_UNSOL_RESTRICTED_STATE_CHANGED, state);
 
@@ -580,7 +580,7 @@
     }
 
     public void enterEmergencyCallbackMode(int indicationType) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_ENTER_EMERGENCY_CALLBACK_MODE);
 
@@ -590,7 +590,7 @@
     }
 
     public void cdmaCallWaiting(int indicationType, CdmaCallWaiting callWaitingRecord) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         // todo: create a CdmaCallWaitingNotification constructor that takes in these fields to make
         // sure no fields are missing
@@ -614,7 +614,7 @@
     }
 
     public void cdmaOtaProvisionStatus(int indicationType, int status) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         int response[] = new int[1];
         response[0] = status;
@@ -628,7 +628,7 @@
 
     public void cdmaInfoRec(int indicationType,
                             android.hardware.radio.V1_0.CdmaInformationRecords records) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         int numberOfInfoRecs = records.infoRec.size();
         for (int i = 0; i < numberOfInfoRecs; i++) {
@@ -724,7 +724,7 @@
     }
 
     public void indicateRingbackTone(int indicationType, boolean start) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLogvRet(RIL_UNSOL_RINGBACK_TONE, start);
 
@@ -732,7 +732,7 @@
     }
 
     public void resendIncallMute(int indicationType) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESEND_INCALL_MUTE);
 
@@ -740,7 +740,7 @@
     }
 
     public void cdmaSubscriptionSourceChanged(int indicationType, int cdmaSource) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         int response[] = new int[1];
         response[0] = cdmaSource;
@@ -754,7 +754,7 @@
     }
 
     public void cdmaPrlChanged(int indicationType, int version) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         int response[] = new int[1];
         response[0] = version;
@@ -766,7 +766,7 @@
     }
 
     public void exitEmergencyCallbackMode(int indicationType) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_EXIT_EMERGENCY_CALLBACK_MODE);
 
@@ -774,7 +774,7 @@
     }
 
     public void rilConnected(int indicationType) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RIL_CONNECTED);
 
@@ -787,7 +787,7 @@
     }
 
     public void voiceRadioTechChanged(int indicationType, int rat) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         int response[] = new int[1];
         response[0] = rat;
@@ -803,35 +803,35 @@
     /** Get unsolicited message for cellInfoList */
     public void cellInfoList(int indicationType,
             ArrayList<android.hardware.radio.V1_0.CellInfo> records) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
         responseCellInfoList(records);
     }
 
     /** Get unsolicited message for cellInfoList using HAL V1_2 */
     public void cellInfoList_1_2(int indicationType,
             ArrayList<android.hardware.radio.V1_2.CellInfo> records) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
         responseCellInfoList(records);
     }
 
     /** Get unsolicited message for cellInfoList using HAL V1_4 */
     public void cellInfoList_1_4(int indicationType,
             ArrayList<android.hardware.radio.V1_4.CellInfo> records) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
         responseCellInfoList(records);
     }
 
     /** Get unsolicited message for cellInfoList using HAL V1_5 */
     public void cellInfoList_1_5(int indicationType,
             ArrayList<android.hardware.radio.V1_5.CellInfo> records) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
         responseCellInfoList(records);
     }
 
     /** Get unsolicited message for cellInfoList using HAL V1_5 */
     public void cellInfoList_1_6(int indicationType,
             ArrayList<android.hardware.radio.V1_6.CellInfo> records) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
         responseCellInfoList(records);
     }
 
@@ -843,7 +843,7 @@
 
     /** Get unsolicited message for uicc applications enablement changes. */
     public void uiccApplicationsEnablementChanged(int indicationType, boolean enabled) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         if (mRil.isLogOrTrace()) {
             mRil.unsljLogRet(RIL_UNSOL_UICC_APPLICATIONS_ENABLEMENT_CHANGED, enabled);
@@ -883,7 +883,7 @@
     }
 
     public void imsNetworkStateChanged(int indicationType) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_IMS_NETWORK_STATE_CHANGED);
 
@@ -891,7 +891,7 @@
     }
 
     public void subscriptionStatusChanged(int indicationType, boolean activate) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         int response[] = new int[1];
         response[0] = activate ? 1 : 0;
@@ -905,7 +905,7 @@
     }
 
     public void srvccStateNotify(int indicationType, int state) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         int response[] = new int[1];
         response[0] = state;
@@ -921,7 +921,7 @@
     public void hardwareConfigChanged(
             int indicationType,
             ArrayList<android.hardware.radio.V1_0.HardwareConfig> configs) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         ArrayList<HardwareConfig> response = RILUtils.convertHalHardwareConfigList(configs);
 
@@ -933,7 +933,7 @@
 
     public void radioCapabilityIndication(int indicationType,
                                           android.hardware.radio.V1_0.RadioCapability rc) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         RadioCapability response = RILUtils.convertHalRadioCapability(rc, mRil);
 
@@ -944,7 +944,7 @@
     }
 
     public void onSupplementaryServiceIndication(int indicationType, StkCcUnsolSsResult ss) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         int num;
         SsData ssData = new SsData();
@@ -992,7 +992,7 @@
     }
 
     public void stkCallControlAlphaNotify(int indicationType, String alpha) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_STK_CC_ALPHA_NOTIFY, alpha);
 
@@ -1002,7 +1002,7 @@
     }
 
     public void lceData(int indicationType, LceDataInfo lce) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         List<LinkCapacityEstimate> response = RILUtils.convertHalLceData(lce);
 
@@ -1014,7 +1014,7 @@
     }
 
     public void pcoData(int indicationType, PcoDataInfo pco) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         PcoData response = new PcoData(pco.cid, pco.bearerProto, pco.pcoId,
                 RILUtils.arrayListToPrimitiveArray(pco.contents));
@@ -1025,7 +1025,7 @@
     }
 
     public void modemReset(int indicationType, String reason) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_MODEM_RESTART, reason);
 
@@ -1038,7 +1038,7 @@
      * @param indicationType RadioIndicationType
      */
     public void carrierInfoForImsiEncryption(int indicationType) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         if (mRil.isLogOrTrace()) {
             mRil.unsljLogRet(RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION, null);
@@ -1055,7 +1055,7 @@
      */
     public void keepaliveStatus(
             int indicationType, android.hardware.radio.V1_1.KeepaliveStatus halStatus) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         if (mRil.isLogOrTrace()) {
             mRil.unsljLogRet(
@@ -1074,7 +1074,7 @@
      * @param indicationType RadioIndicationType
      */
     public void simPhonebookChanged(int indicationType) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         if (mRil.isLogOrTrace()) {
             mRil.unsljLog(RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED);
@@ -1091,7 +1091,7 @@
      */
     public void simPhonebookRecordsReceived(int indicationType, byte status,
             ArrayList<PhonebookRecordInfo> records) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         List<SimPhonebookRecord> simPhonebookRecords = new ArrayList<>();
 
@@ -1124,7 +1124,7 @@
             android.hardware.radio.V1_5.CellIdentity cellIdentity, String chosenPlmn,
             @NetworkRegistrationInfo.Domain int domain,
             int causeCode, int additionalCauseCode) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
         CellIdentity ci = RILUtils.convertHalCellIdentity(cellIdentity);
         if (ci == null
                 || TextUtils.isEmpty(chosenPlmn)
@@ -1132,7 +1132,7 @@
                 || (domain & ~NetworkRegistrationInfo.DOMAIN_CS_PS) != 0
                 || causeCode < 0 || additionalCauseCode < 0
                 || (causeCode == Integer.MAX_VALUE && additionalCauseCode == Integer.MAX_VALUE)) {
-            reportAnomaly(
+            AnomalyReporter.reportAnomaly(
                     UUID.fromString("f16e5703-6105-4341-9eb3-e68189156eb4"),
                             "Invalid registrationFailed indication");
 
@@ -1155,10 +1155,10 @@
     public void barringInfoChanged(int indicationType,
             android.hardware.radio.V1_5.CellIdentity cellIdentity,
             ArrayList<android.hardware.radio.V1_5.BarringInfo> barringInfos) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         if (cellIdentity == null || barringInfos == null) {
-            reportAnomaly(
+            AnomalyReporter.reportAnomaly(
                     UUID.fromString("645b16bb-c930-4c1c-9c5d-568696542e05"),
                             "Invalid barringInfoChanged indication");
 
@@ -1224,22 +1224,29 @@
                     android.hardware.radio.V1_6.PhysicalChannelConfig config =
                             (android.hardware.radio.V1_6.PhysicalChannelConfig) obj;
                     PhysicalChannelConfig.Builder builder = new PhysicalChannelConfig.Builder();
+                    int band = PhysicalChannelConfig.BAND_UNKNOWN;
                     switch (config.band.getDiscriminator()) {
                         case Band.hidl_discriminator.geranBand:
-                            builder.setBand(config.band.geranBand());
+                            band = config.band.geranBand();
                             break;
                         case Band.hidl_discriminator.utranBand:
-                            builder.setBand(config.band.utranBand());
+                            band = config.band.utranBand();
                             break;
                         case Band.hidl_discriminator.eutranBand:
-                            builder.setBand(config.band.eutranBand());
+                            band = config.band.eutranBand();
                             break;
                         case Band.hidl_discriminator.ngranBand:
-                            builder.setBand(config.band.ngranBand());
+                            band = config.band.ngranBand();
                             break;
                         default:
                             mRil.riljLoge("Unsupported band " + config.band.getDiscriminator());
                     }
+                    if (band == PhysicalChannelConfig.BAND_UNKNOWN) {
+                        mRil.riljLoge("Unsupported unknown band.");
+                        return;
+                    } else {
+                        builder.setBand(band);
+                    }
                     response.add(builder.setCellConnectionStatus(
                             RILUtils.convertHalCellConnectionStatus(config.status))
                             .setDownlinkChannelNumber(config.downlinkChannelNumber)
@@ -1256,8 +1263,9 @@
                 }
             }
         } catch (IllegalArgumentException iae) {
-            reportAnomaly(UUID.fromString("918f0970-9aa9-4bcd-a28e-e49a83fe77d5"),
-                    "RIL reported invalid PCC (HIDL)");
+            AnomalyReporter.reportAnomaly(
+                    UUID.fromString("918f0970-9aa9-4bcd-a28e-e49a83fe77d5"),
+                            "RIL reported invalid PCC (HIDL)");
             mRil.riljLoge("Invalid PhysicalChannelConfig " + iae);
             return;
         }
@@ -1270,7 +1278,7 @@
 
     private void responseNetworkScan(int indicationType,
             android.hardware.radio.V1_1.NetworkScanResult result) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         ArrayList<CellInfo> cellInfos =
                 RILUtils.convertHalCellInfoList(new ArrayList<>(result.networkInfos));
@@ -1281,7 +1289,7 @@
 
     private void responseNetworkScan_1_2(int indicationType,
             android.hardware.radio.V1_2.NetworkScanResult result) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         ArrayList<CellInfo> cellInfos =
                 RILUtils.convertHalCellInfoList(new ArrayList<>(result.networkInfos));
@@ -1292,7 +1300,7 @@
 
     private void responseNetworkScan_1_4(int indicationType,
             android.hardware.radio.V1_4.NetworkScanResult result) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         ArrayList<CellInfo> cellInfos =
                 RILUtils.convertHalCellInfoList(new ArrayList<>(result.networkInfos));
@@ -1303,7 +1311,7 @@
 
     private void responseNetworkScan_1_5(int indicationType,
             android.hardware.radio.V1_5.NetworkScanResult result) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         ArrayList<CellInfo> cellInfos =
                 RILUtils.convertHalCellInfoList(new ArrayList<>(result.networkInfos));
@@ -1314,7 +1322,7 @@
 
     private void responseNetworkScan_1_6(int indicationType,
             android.hardware.radio.V1_6.NetworkScanResult result) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         ArrayList<CellInfo> cellInfos =
                 RILUtils.convertHalCellInfoList(new ArrayList<>(result.networkInfos));
@@ -1324,7 +1332,7 @@
     }
 
     private void responseDataCallListChanged(int indicationType, List<?> dcList) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_DATA_CALL_LIST_CHANGED, dcList);
 
@@ -1334,17 +1342,11 @@
     }
 
     private void responseApnUnthrottled(int indicationType, String apn) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_UNTHROTTLE_APN, apn);
 
         mRil.mApnUnthrottledRegistrants.notifyRegistrants(
                 new AsyncResult(null, apn, null));
     }
-
-    private void reportAnomaly(UUID uuid, String msg) {
-        Phone phone = mRil.mPhoneId == null ? null : PhoneFactory.getPhone(mRil.mPhoneId);
-        int carrierId = phone == null ? UNKNOWN_CARRIER_ID : phone.getCarrierId();
-        AnomalyReporter.reportAnomaly(uuid, msg, carrierId);
-    }
 }
diff --git a/src/java/com/android/internal/telephony/RadioInterfaceCapabilityController.java b/src/java/com/android/internal/telephony/RadioInterfaceCapabilityController.java
index 04c6b54..bab4d12 100644
--- a/src/java/com/android/internal/telephony/RadioInterfaceCapabilityController.java
+++ b/src/java/com/android/internal/telephony/RadioInterfaceCapabilityController.java
@@ -28,6 +28,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.telephony.Rlog;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.Collections;
 import java.util.Set;
 
@@ -164,6 +166,13 @@
         }
     }
 
+    /**
+     * Dump the fields of the instance
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("mRadioConfig=" + mRadioConfig);
+    }
+
     private static void log(final String s) {
         Rlog.d(LOG_TAG, s);
     }
diff --git a/src/java/com/android/internal/telephony/RadioMessagingProxy.java b/src/java/com/android/internal/telephony/RadioMessagingProxy.java
index e68e957..69ccf36 100644
--- a/src/java/com/android/internal/telephony/RadioMessagingProxy.java
+++ b/src/java/com/android/internal/telephony/RadioMessagingProxy.java
@@ -36,13 +36,23 @@
      * Set IRadioMessaging as the AIDL implementation for RadioServiceProxy
      * @param halVersion Radio HAL version
      * @param messaging IRadioMessaging implementation
+     *
+     * @return updated HAL version
      */
-    public void setAidl(HalVersion halVersion,
+    public HalVersion setAidl(HalVersion halVersion,
             android.hardware.radio.messaging.IRadioMessaging messaging) {
-        mHalVersion = halVersion;
+        HalVersion version = halVersion;
+        try {
+            version = RIL.getServiceHalVersion(messaging.getInterfaceVersion());
+        } catch (RemoteException e) {
+            Rlog.e(TAG, "setAidl: " + e);
+        }
+        mHalVersion = version;
         mMessagingProxy = messaging;
         mIsAidl = true;
-        Rlog.d(TAG, "AIDL initialized");
+
+        Rlog.d(TAG, "AIDL initialized mHalVersion=" + mHalVersion);
+        return mHalVersion;
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/RadioModemProxy.java b/src/java/com/android/internal/telephony/RadioModemProxy.java
index 7aaf727..4178293 100644
--- a/src/java/com/android/internal/telephony/RadioModemProxy.java
+++ b/src/java/com/android/internal/telephony/RadioModemProxy.java
@@ -31,13 +31,23 @@
      * Set IRadioModem as the AIDL implementation for RadioServiceProxy
      * @param halVersion Radio HAL version
      * @param modem IRadioModem implementation
+     *
+     * @return updated HAL version
      */
-    public void setAidl(HalVersion halVersion,
+    public HalVersion setAidl(HalVersion halVersion,
             android.hardware.radio.modem.IRadioModem modem) {
-        mHalVersion = halVersion;
+        HalVersion version = halVersion;
+        try {
+            version = RIL.getServiceHalVersion(modem.getInterfaceVersion());
+        } catch (RemoteException e) {
+            Rlog.e(TAG, "setAidl: " + e);
+        }
+        mHalVersion = version;
         mModemProxy = modem;
         mIsAidl = true;
-        Rlog.d(TAG, "AIDL initialized");
+
+        Rlog.d(TAG, "AIDL initialized mHalVersion=" + mHalVersion);
+        return mHalVersion;
     }
 
     /**
@@ -110,6 +120,19 @@
     }
 
     /**
+     * Call IRadioModem#getImei
+     *
+     * @param serial Serial number of request
+     * @throws RemoteException
+     */
+    public void getImei(int serial) throws RemoteException {
+        if (isEmpty()) return;
+        if (isAidl()) {
+            mModemProxy.getImei(serial);
+        }
+    }
+
+    /**
      * Call IRadioModem#getHardwareConfig
      * @param serial Serial number of request
      * @throws RemoteException
diff --git a/src/java/com/android/internal/telephony/RadioNetworkProxy.java b/src/java/com/android/internal/telephony/RadioNetworkProxy.java
index b881035..246c2e0 100644
--- a/src/java/com/android/internal/telephony/RadioNetworkProxy.java
+++ b/src/java/com/android/internal/telephony/RadioNetworkProxy.java
@@ -65,13 +65,23 @@
      * Set IRadioNetwork as the AIDL implementation for RadioServiceProxy
      * @param halVersion Radio HAL version
      * @param network IRadioNetwork implementation
+     *
+     * @return updated HAL version
      */
-    public void setAidl(HalVersion halVersion,
+    public HalVersion setAidl(HalVersion halVersion,
             android.hardware.radio.network.IRadioNetwork network) {
-        mHalVersion = halVersion;
+        HalVersion version = halVersion;
+        try {
+            version = RIL.getServiceHalVersion(network.getInterfaceVersion());
+        } catch (RemoteException e) {
+            Rlog.e(TAG, "setAidl: " + e);
+        }
+        mHalVersion = version;
         mNetworkProxy = network;
         mIsAidl = true;
-        Rlog.d(TAG, "AIDL initialized");
+
+        Rlog.d(TAG, "AIDL initialized mHalVersion=" + mHalVersion);
+        return mHalVersion;
     }
 
     /**
@@ -828,4 +838,127 @@
         }
         // Only supported on AIDL.
     }
+
+    /**
+     * Set the Emergency Mode
+     *
+     * @param serial Serial number of the request.
+     * @param emcModeType Defines the radio emergency mode type.
+     * @throws RemoteException
+     */
+    public void setEmergencyMode(int serial, int emcModeType) throws RemoteException {
+        if (isEmpty()) return;
+        if (isAidl()) {
+            mNetworkProxy.setEmergencyMode(serial, emcModeType);
+        }
+        // Only supported on AIDL.
+    }
+
+    /**
+     * Triggers an Emergency network scan.
+     *
+     * @param serial Serial number of the request.
+     * @param scanRequest Contains the preferred networks and type of service to be scanned.
+     * @throws RemoteException
+     */
+    public void triggerEmergencyNetworkScan(int serial,
+            android.hardware.radio.network.EmergencyNetworkScanTrigger scanRequest)
+            throws RemoteException {
+        if (isEmpty()) return;
+        if (isAidl()) {
+            mNetworkProxy.triggerEmergencyNetworkScan(serial, scanRequest);
+        }
+        // Only supported on AIDL.
+    }
+
+    /**
+     * Cancels ongoing Emergency network scan
+     *
+     * @param serial Serial number of the request.
+     * @param resetScan Indicates how the next {@link #triggerEmergencyNetworkScan} should work.
+     *        If {@code true}, then the modem shall start the new scan from the beginning,
+     *        otherwise the modem shall resume from the last search.
+     *
+     * @throws RemoteException
+     */
+    public void cancelEmergencyNetworkScan(int serial, boolean resetScan) throws RemoteException {
+        if (isEmpty()) return;
+        if (isAidl()) {
+            mNetworkProxy.cancelEmergencyNetworkScan(serial, resetScan);
+        }
+        // Only supported on AIDL.
+    }
+
+    /**
+     * Exits ongoing Emergency Mode
+     *
+     * @param serial Serial number of the request.
+     * @throws RemoteException
+     */
+    public void exitEmergencyMode(int serial) throws RemoteException {
+        if (isEmpty()) return;
+        if (isAidl()) {
+            mNetworkProxy.exitEmergencyMode(serial);
+        }
+        // Only supported on AIDL.
+    }
+
+    /**
+     * Set if null ciphering / null integrity is permitted.
+     *
+     * @param serial Serial number of the request.
+     * @param enabled true if null modes are allowed, false otherwise
+     * @throws RemoteException
+     */
+    public void setNullCipherAndIntegrityEnabled(int serial,
+            boolean enabled) throws RemoteException {
+        if (isEmpty()) return;
+        if (isAidl()) {
+            mNetworkProxy.setNullCipherAndIntegrityEnabled(serial, enabled);
+        }
+        // Only supported on AIDL.
+    }
+
+    /**
+     * Get if null ciphering / null integrity is permitted.
+     * @param serial Serial number of the request.
+     * @throws RemoteException
+     *
+     */
+    public void isNullCipherAndIntegrityEnabled(int serial) throws RemoteException {
+        if (isEmpty()) return;
+        if (isAidl()) {
+            mNetworkProxy.isNullCipherAndIntegrityEnabled(serial);
+        }
+        // Only supported on AIDL.
+    }
+
+    /**
+     * Checks whether N1 mode is enabled.
+     *
+     * @param serial Serial number of the request.
+     * @throws RemoteException
+     */
+    public void isN1ModeEnabled(int serial) throws RemoteException {
+        if (isEmpty()) return;
+        if (isAidl()) {
+            mNetworkProxy.isN1ModeEnabled(serial);
+        }
+        // Only supported on AIDL.
+    }
+
+    /**
+     * Enables or disables N1 mode.
+     *
+     * @param serial Serial number of request.
+     * @param enable Indicates whether to enable N1 mode or not.
+     * @throws RemoteException
+     */
+    public void setN1ModeEnabled(int serial, boolean enable) throws RemoteException {
+        if (isEmpty()) return;
+        if (isAidl()) {
+            mNetworkProxy.setN1ModeEnabled(serial, enable);
+        }
+        // Only supported on AIDL.
+    }
 }
diff --git a/src/java/com/android/internal/telephony/RadioServiceProxy.java b/src/java/com/android/internal/telephony/RadioServiceProxy.java
index 8650dd0..4257327 100644
--- a/src/java/com/android/internal/telephony/RadioServiceProxy.java
+++ b/src/java/com/android/internal/telephony/RadioServiceProxy.java
@@ -78,4 +78,9 @@
         if (isEmpty()) return;
         if (!isAidl()) mRadioProxy.responseAcknowledgement();
     }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "[mHalVersion=" + mHalVersion + ']';
+    }
 }
diff --git a/src/java/com/android/internal/telephony/RadioSimProxy.java b/src/java/com/android/internal/telephony/RadioSimProxy.java
index da5b660..7c8ee7b 100644
--- a/src/java/com/android/internal/telephony/RadioSimProxy.java
+++ b/src/java/com/android/internal/telephony/RadioSimProxy.java
@@ -41,12 +41,22 @@
      * Set IRadioSim as the AIDL implementation for RadioServiceProxy
      * @param halVersion Radio HAL version
      * @param sim IRadioSim implementation
+     *
+     * @return updated HAL version
      */
-    public void setAidl(HalVersion halVersion, android.hardware.radio.sim.IRadioSim sim) {
-        mHalVersion = halVersion;
+    public HalVersion setAidl(HalVersion halVersion, android.hardware.radio.sim.IRadioSim sim) {
+        HalVersion version = halVersion;
+        try {
+            version = RIL.getServiceHalVersion(sim.getInterfaceVersion());
+        } catch (RemoteException e) {
+            Rlog.e(TAG, "setAidl: " + e);
+        }
+        mHalVersion = version;
         mSimProxy = sim;
         mIsAidl = true;
-        Rlog.d(TAG, "AIDL initialized");
+
+        Rlog.d(TAG, "AIDL initialized mHalVersion=" + mHalVersion);
+        return mHalVersion;
     }
 
     /**
@@ -262,14 +272,24 @@
     }
 
     /**
-     * Call IRadioSim#iccCloseLogicalChannel
+     * Call IRadioSim#iccCloseLogicalChannelWithSessionInfo
      * @param serial Serial number of request
      * @param channelId Channel ID of the channel to be closed
+     * @param isEs10 Whether the logical channel is opened for performing ES10 operations.
      * @throws RemoteException
      */
-    public void iccCloseLogicalChannel(int serial, int channelId) throws RemoteException {
+    public void iccCloseLogicalChannel(int serial,
+            int channelId, boolean isEs10) throws RemoteException {
         if (isEmpty()) return;
         if (isAidl()) {
+            if (mHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_2_1)) {
+                android.hardware.radio.sim.SessionInfo info =
+                        new android.hardware.radio.sim.SessionInfo();
+                info.sessionId = channelId;
+                info.isEs10 = isEs10;
+                mSimProxy.iccCloseLogicalChannelWithSessionInfo(serial, info);
+                return;
+            }
             mSimProxy.iccCloseLogicalChannel(serial, channelId);
         } else {
             mRadioProxy.iccCloseLogicalChannel(serial, channelId);
@@ -352,7 +372,8 @@
         if (isEmpty()) return;
         if (isAidl()) {
             mSimProxy.iccTransmitApduBasicChannel(serial,
-                    RILUtils.convertToHalSimApduAidl(0, cla, instruction, p1, p2, p3, data));
+                    RILUtils.convertToHalSimApduAidl(0, cla, instruction, p1, p2, p3, data,
+                            false, mHalVersion));
         } else {
             mRadioProxy.iccTransmitApduBasicChannel(serial,
                     RILUtils.convertToHalSimApdu(0, cla, instruction, p1, p2, p3, data));
@@ -373,10 +394,29 @@
      */
     public void iccTransmitApduLogicalChannel(int serial, int channel, int cla, int instruction,
             int p1, int p2, int p3, String data) throws RemoteException {
+        iccTransmitApduLogicalChannel(serial, channel, cla, instruction, p1, p2, p3, data, false);
+    }
+
+    /**
+     * Call IRadioSim#iccTransmitApduLogicalChannel
+     * @param serial Serial number of request
+     * @param channel Channel ID of the channel to use for communication
+     * @param cla Class of the command
+     * @param instruction Instruction of the command
+     * @param p1 P1 value of the command
+     * @param p2 P2 value of the command
+     * @param p3 P3 value of the command
+     * @param data Data to be sent
+     * @param isEs10Command APDU is an isEs10 command or not
+     * @throws RemoteException
+     */
+    public void iccTransmitApduLogicalChannel(int serial, int channel, int cla, int instruction,
+            int p1, int p2, int p3, String data, boolean isEs10Command) throws RemoteException {
         if (isEmpty()) return;
         if (isAidl()) {
             mSimProxy.iccTransmitApduLogicalChannel(serial,
-                    RILUtils.convertToHalSimApduAidl(channel, cla, instruction, p1, p2, p3, data));
+                    RILUtils.convertToHalSimApduAidl(channel, cla, instruction, p1, p2, p3, data,
+                            isEs10Command, mHalVersion));
         } else {
             mRadioProxy.iccTransmitApduLogicalChannel(serial,
                     RILUtils.convertToHalSimApdu(channel, cla, instruction, p1, p2, p3, data));
diff --git a/src/java/com/android/internal/telephony/RadioVoiceProxy.java b/src/java/com/android/internal/telephony/RadioVoiceProxy.java
index 6ac603b..7f46424 100644
--- a/src/java/com/android/internal/telephony/RadioVoiceProxy.java
+++ b/src/java/com/android/internal/telephony/RadioVoiceProxy.java
@@ -35,12 +35,23 @@
      * Set IRadioVoice as the AIDL implementation for RadioServiceProxy
      * @param halVersion Radio HAL version
      * @param voice IRadioVoice implementation
+     *
+     * @return updated HAL version
      */
-    public void setAidl(HalVersion halVersion, android.hardware.radio.voice.IRadioVoice voice) {
-        mHalVersion = halVersion;
+    public HalVersion setAidl(HalVersion halVersion,
+            android.hardware.radio.voice.IRadioVoice voice) {
+        HalVersion version = halVersion;
+        try {
+            version = RIL.getServiceHalVersion(voice.getInterfaceVersion());
+        } catch (RemoteException e) {
+            Rlog.e(TAG, "setAidl: " + e);
+        }
+        mHalVersion = version;
         mVoiceProxy = voice;
         mIsAidl = true;
-        Rlog.d(TAG, "AIDL initialized");
+
+        Rlog.d(TAG, "AIDL initialized mHalVersion=" + mHalVersion);
+        return mHalVersion;
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/RatRatcheter.java b/src/java/com/android/internal/telephony/RatRatcheter.java
index bea2c2a..aff62ae 100644
--- a/src/java/com/android/internal/telephony/RatRatcheter.java
+++ b/src/java/com/android/internal/telephony/RatRatcheter.java
@@ -141,15 +141,24 @@
         synchronized (mRatFamilyMap) {
             // Either the two technologies are the same or their families must be non-null
             // and the same.
+            // To Fix Missing Null check
+            if (ss1.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
+                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN) == null
+                    || ss2.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
+                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN) == null) {
+                return false;
+            }
+
             int dataRat1 = ServiceState.networkTypeToRilRadioTechnology(
                     ss1.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
-                            AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                           AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
                             .getAccessNetworkTechnology());
             int dataRat2 = ServiceState.networkTypeToRilRadioTechnology(
                     ss2.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
-                            AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                           AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
                             .getAccessNetworkTechnology());
 
+
             // The api getAccessNetworkTechnology@NetworkRegistrationInfo always returns LTE though
             // data rat is LTE CA. Because it uses mIsUsingCarrierAggregation to indicate whether
             // it is LTE CA or not. However, we need its actual data rat to check if they are the
diff --git a/src/java/com/android/internal/telephony/SMSDispatcher.java b/src/java/com/android/internal/telephony/SMSDispatcher.java
index f1afddd..a78242a 100644
--- a/src/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/SMSDispatcher.java
@@ -27,6 +27,9 @@
 import android.app.AlertDialog;
 import android.app.PendingIntent;
 import android.app.PendingIntent.CanceledException;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -39,6 +42,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.AsyncResult;
@@ -86,6 +90,7 @@
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.uicc.IccRecords;
+import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
@@ -145,6 +150,8 @@
     /** New status report received. */
     protected static final int EVENT_NEW_SMS_STATUS_REPORT = 10;
 
+    /** Retry Sending RP-SMMA Notification */
+    protected static final int EVENT_RETRY_SMMA = 11;
     // other
     protected static final int EVENT_NEW_ICC_SMS = 14;
     protected static final int EVENT_ICC_CHANGED = 15;
@@ -156,6 +163,17 @@
     /** Handle SIM loaded  */
     private static final int EVENT_SIM_LOADED = 18;
 
+    /**
+     * When this change is enabled, more specific values of SMS sending error code
+     * {@link SmsManager#Result} will be returned to the SMS Apps.
+     *
+     * Refer to {@link SMSDispatcher#rilErrorToSmsManagerResult} fore more details of the new values
+     * of SMS sending error code that will be returned.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    static final long ADD_MORE_SMS_SENDING_ERROR_CODES = 250017070L;
+
     @UnsupportedAppUsage
     protected Phone mPhone;
     @UnsupportedAppUsage
@@ -167,9 +185,18 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     protected final TelephonyManager mTelephonyManager;
     protected final LocalLog mLocalLog = new LocalLog(16);
+    protected final LocalLog mSmsOutgoingErrorCodes = new LocalLog(10);
 
     /** Maximum number of times to retry sending a failed SMS. */
     protected static final int MAX_SEND_RETRIES = 3;
+
+    /** Retransmitted Flag as specified in section 6.3.1.2 in TS 124011
+     * true:  RP-SMMA Retried once and no more transmissions are permitted
+     * false: not retried at all and at least another transmission of the RP-SMMA message
+     * is currently permitted
+     */
+    protected boolean mRPSmmaRetried = false;
+
     /** Delay before next send attempt on a failed SMS, in milliseconds. */
     @VisibleForTesting
     public static final int SEND_RETRY_DELAY = 2000;
@@ -300,6 +327,26 @@
     protected abstract String getFormat();
 
     /**
+     * Gets the maximum number of times the SMS can be retried upon Failure,
+     * from the {@link android.telephony.CarrierConfigManager}
+     *
+     * @return the default maximum number of times SMS can be sent
+     */
+    protected int getMaxSmsRetryCount() {
+        return MAX_SEND_RETRIES;
+    }
+
+    /**
+     * Gets the Time delay before next send attempt on a failed SMS,
+     * from the {@link android.telephony.CarrierConfigManager}
+     *
+     * @return the Time in miiliseconds for delay before next send attempt on a failed SMS
+     */
+    protected int getSmsRetryDelayValue() {
+        return SEND_RETRY_DELAY;
+    }
+
+    /**
      * Called when a status report is received. This should correspond to a previously successful
      * SEND.
      *
@@ -400,14 +447,10 @@
                  */
                 mMessageRef = getTpmrValueFromSIM();
                 if (mMessageRef == -1) {
-                    if (mPhone.isSubscriptionManagerServiceEnabled()) {
-                        SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
-                                .getSubscriptionInfoInternal(msg.arg1);
-                        if (subInfo != null) {
-                            mMessageRef = subInfo.getLastUsedTPMessageReference();
-                        }
-                    } else {
-                        mMessageRef = SubscriptionController.getInstance().getMessageRef(msg.arg1);
+                    SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
+                            .getSubscriptionInfoInternal(msg.arg1);
+                    if (subInfo != null) {
+                        mMessageRef = subInfo.getLastUsedTPMessageReference();
                     }
                 }
                 break;
@@ -430,12 +473,8 @@
         updateSIMLastTPMRValue(mMessageRef);
         final long identity = Binder.clearCallingIdentity();
         try {
-            if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-                SubscriptionManagerService.getInstance()
-                        .setLastUsedTPMessageReference(getSubId(), mMessageRef);
-            } else {
-                SubscriptionController.getInstance().updateMessageRef(getSubId(), mMessageRef);
-            }
+            SubscriptionManagerService.getInstance()
+                    .setLastUsedTPMessageReference(getSubId(), mMessageRef);
         } catch (SecurityException e) {
             Rlog.e(TAG, "Security Exception caused on messageRef updation to DB " + e.getMessage());
         } finally {
@@ -467,18 +506,43 @@
     }
 
     /**
-     *  Returns the next TP message Reference value incremented by 1 for every sms sent .
-     *  once a max of 255 is reached TP message Reference is reset to 0.
+     * Returns the next TP message Reference value incremented by 1 for every sms sent .
+     * once a max of 255 is reached TP message Reference is reset to 0.
      *
-     *  @return messageRef TP message Reference value
+     * @return messageRef TP message Reference value
      */
     public int nextMessageRef() {
+        if (!isMessageRefIncrementViaTelephony()) {
+            return 0;
+        }
+
         mMessageRef = (mMessageRef + 1) % 256;
         updateTPMessageReference();
         return mMessageRef;
     }
 
     /**
+     * As modem is using the last used TP-MR value present in SIM card, increment of
+     * messageRef(TP-MR) value should be prevented (config_stk_sms_send_support set to false)
+     * at telephony framework. In future, config_stk_sms_send_support flag will be enabled
+     * so that messageRef(TP-MR) increment will be done at framework side only.
+     *
+     * TODO:- Need to have new flag to control writing TP-MR value to SIM or shared prefrence.
+     */
+    public boolean isMessageRefIncrementViaTelephony() {
+        boolean isMessageRefIncrementEnabled = false;
+        try {
+            isMessageRefIncrementEnabled = mContext.getResources().getBoolean(
+                    com.android.internal.R.bool.config_stk_sms_send_support);
+        } catch (NotFoundException e) {
+            Rlog.e(TAG, "isMessageRefIncrementViaTelephony NotFoundException Exception");
+        }
+
+        Rlog.i(TAG, "bool.config_stk_sms_send_support= " + isMessageRefIncrementEnabled);
+        return isMessageRefIncrementEnabled;
+    }
+
+    /**
      * Use the carrier messaging service to send a data or text SMS.
      */
     protected abstract class SmsSender extends Handler {
@@ -688,26 +752,50 @@
         @Override
         public void onSendSmsComplete(int result, int messageRef) {
             Rlog.d(TAG, "onSendSmsComplete: result=" + result + " messageRef=" + messageRef);
-            if (mCallbackCalled) {
-                logWithLocalLog("onSendSmsComplete: unexpected call");
-                AnomalyReporter.reportAnomaly(sAnomalyUnexpectedCallback,
-                        "Unexpected onSendSmsComplete", mPhone.getCarrierId());
+            if (cleanupOnSendSmsComplete("onSendSmsComplete")) {
                 return;
             }
-            mCallbackCalled = true;
+
             final long identity = Binder.clearCallingIdentity();
             try {
-                mSmsSender.mCarrierMessagingServiceWrapper.disconnect();
                 processSendSmsResponse(mSmsSender.getSmsTracker(), result, messageRef);
-                mSmsSender.removeTimeout();
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
         }
 
+        /**
+         * This method should be called only once.
+         */
         @Override
         public void onSendMultipartSmsComplete(int result, int[] messageRefs) {
-            Rlog.e(TAG, "Unexpected onSendMultipartSmsComplete call with result: " + result);
+            Rlog.d(TAG, "onSendMultipartSmsComplete: result=" + result + " messageRefs="
+                    + Arrays.toString(messageRefs));
+            if (cleanupOnSendSmsComplete("onSendMultipartSmsComplete")) {
+                return;
+            }
+
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                processSendMultipartSmsResponse(mSmsSender.getSmsTrackers(), result, messageRefs);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        private boolean cleanupOnSendSmsComplete(String callingFunction) {
+            if (mCallbackCalled) {
+                logWithLocalLog(callingFunction + ": unexpected call");
+                AnomalyReporter.reportAnomaly(sAnomalyUnexpectedCallback,
+                        "Unexpected " + callingFunction, mPhone.getCarrierId());
+                return true;
+            }
+
+            mCallbackCalled = true;
+            mSmsSender.removeTimeout();
+            mSmsSender.mCarrierMessagingServiceWrapper.disconnect();
+
+            return false;
         }
 
         @Override
@@ -782,8 +870,7 @@
         }
 
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-        void sendSmsByCarrierApp(String carrierPackageName,
-                                 MultipartSmsSenderCallback senderCallback) {
+        void sendSmsByCarrierApp(String carrierPackageName, SmsSenderCallback senderCallback) {
             super.sendSmsByCarrierApp(carrierPackageName, senderCallback);
         }
 
@@ -831,69 +918,6 @@
         }
     }
 
-    /**
-     * Callback for MultipartSmsSender from the carrier messaging service.
-     * Once the result is ready, the carrier messaging service connection is disposed.
-     */
-    private final class MultipartSmsSenderCallback implements CarrierMessagingCallback {
-        private final MultipartSmsSender mSmsSender;
-        private boolean mCallbackCalled = false;
-
-        MultipartSmsSenderCallback(MultipartSmsSender smsSender) {
-            mSmsSender = smsSender;
-        }
-
-        @Override
-        public void onSendSmsComplete(int result, int messageRef) {
-            Rlog.e(TAG, "Unexpected onSendSmsComplete call with result: " + result);
-        }
-
-        /**
-         * This method should be called only once.
-         */
-        @Override
-        public void onSendMultipartSmsComplete(int result, int[] messageRefs) {
-            Rlog.d(TAG, "onSendMultipartSmsComplete: result=" + result + " messageRefs="
-                    + Arrays.toString(messageRefs));
-            if (mCallbackCalled) {
-                logWithLocalLog("onSendMultipartSmsComplete: unexpected call");
-                AnomalyReporter.reportAnomaly(sAnomalyUnexpectedCallback,
-                        "Unexpected onSendMultipartSmsComplete", mPhone.getCarrierId());
-                return;
-            }
-            mCallbackCalled = true;
-            mSmsSender.removeTimeout();
-            mSmsSender.mCarrierMessagingServiceWrapper.disconnect();
-
-            if (mSmsSender.mTrackers == null) {
-                Rlog.e(TAG, "Unexpected onSendMultipartSmsComplete call with null trackers.");
-                return;
-            }
-
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                processSendMultipartSmsResponse(mSmsSender.mTrackers, result, messageRefs);
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-        }
-
-        @Override
-        public void onReceiveSmsComplete(int result) {
-            Rlog.e(TAG, "Unexpected onReceiveSmsComplete call with result: " + result);
-        }
-
-        @Override
-        public void onSendMmsComplete(int result, byte[] sendConfPdu) {
-            Rlog.e(TAG, "Unexpected onSendMmsComplete call with result: " + result);
-        }
-
-        @Override
-        public void onDownloadMmsComplete(int result) {
-            Rlog.e(TAG, "Unexpected onDownloadMmsComplete call with result: " + result);
-        }
-    }
-
     private void processSendMultipartSmsResponse(
             SmsTracker[] trackers, int result, int[] messageRefs) {
         if (trackers == null) {
@@ -1038,7 +1062,7 @@
                 // This is retry after failure over IMS but voice is not available.
                 // Set retry to max allowed, so no retry is sent and cause
                 // SmsManager.RESULT_ERROR_GENERIC_FAILURE to be returned to app.
-                tracker.mRetryCount = MAX_SEND_RETRIES;
+                tracker.mRetryCount = getMaxSmsRetryCount();
 
                 Rlog.d(TAG, "handleSendComplete: Skipping retry: "
                         + " isIms()=" + isIms()
@@ -1061,7 +1085,7 @@
                         tracker.isFromDefaultSmsApplication(mContext),
                         tracker.getInterval());
             } else if (error == SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY
-                    && tracker.mRetryCount < MAX_SEND_RETRIES) {
+                    && tracker.mRetryCount < getMaxSmsRetryCount()) {
                 // Retry after a delay if needed.
                 // TODO: According to TS 23.040, 9.2.3.6, we should resend
                 //       with the same TP-MR as the failed message, and
@@ -1073,7 +1097,7 @@
                 tracker.mRetryCount++;
                 int errorCode = (smsResponse != null) ? smsResponse.mErrorCode : NO_ERROR_CODE;
                 Message retryMsg = obtainMessage(EVENT_SEND_RETRY, tracker);
-                sendMessageDelayed(retryMsg, SEND_RETRY_DELAY);
+                sendMessageDelayed(retryMsg, getSmsRetryDelayValue());
                 mPhone.getSmsStats().onOutgoingSms(
                         tracker.mImsRetry > 0 /* isOverIms */,
                         SmsConstants.FORMAT_3GPP2.equals(getFormat()),
@@ -1100,8 +1124,31 @@
     }
 
     @SmsManager.Result
-    private static int rilErrorToSmsManagerResult(CommandException.Error rilError,
+    private int rilErrorToSmsManagerResult(CommandException.Error rilError,
             SmsTracker tracker) {
+        mSmsOutgoingErrorCodes.log("rilError: " + rilError
+                + ", MessageId: " + SmsController.formatCrossStackMessageId(tracker.mMessageId));
+
+        ApplicationInfo appInfo = tracker.getAppInfo();
+        if (appInfo == null
+                || !CompatChanges.isChangeEnabled(ADD_MORE_SMS_SENDING_ERROR_CODES, appInfo.uid)) {
+            if (rilError == CommandException.Error.INVALID_RESPONSE
+                    || rilError == CommandException.Error.SIM_PIN2
+                    || rilError == CommandException.Error.SIM_PUK2
+                    || rilError == CommandException.Error.SUBSCRIPTION_NOT_AVAILABLE
+                    || rilError == CommandException.Error.SIM_ERR
+                    || rilError == CommandException.Error.INVALID_SIM_STATE
+                    || rilError == CommandException.Error.NO_SMS_TO_ACK
+                    || rilError == CommandException.Error.SIM_BUSY
+                    || rilError == CommandException.Error.SIM_FULL
+                    || rilError == CommandException.Error.NO_SUBSCRIPTION
+                    || rilError == CommandException.Error.NO_NETWORK_FOUND
+                    || rilError == CommandException.Error.DEVICE_IN_USE
+                    || rilError == CommandException.Error.ABORTED) {
+                return SmsManager.RESULT_ERROR_GENERIC_FAILURE;
+            }
+        }
+
         switch (rilError) {
             case RADIO_NOT_AVAILABLE:
                 return SmsManager.RESULT_RIL_RADIO_NOT_AVAILABLE;
@@ -1151,6 +1198,34 @@
                 return SmsManager.RESULT_RIL_ACCESS_BARRED;
             case BLOCKED_DUE_TO_CALL:
                 return SmsManager.RESULT_RIL_BLOCKED_DUE_TO_CALL;
+            case INVALID_SMSC_ADDRESS:
+                return SmsManager.RESULT_INVALID_SMSC_ADDRESS;
+            case INVALID_RESPONSE:
+                return SmsManager.RESULT_RIL_INVALID_RESPONSE;
+            case SIM_PIN2:
+                return SmsManager.RESULT_RIL_SIM_PIN2;
+            case SIM_PUK2:
+                return SmsManager.RESULT_RIL_SIM_PUK2;
+            case SUBSCRIPTION_NOT_AVAILABLE:
+                return SmsManager.RESULT_RIL_SUBSCRIPTION_NOT_AVAILABLE;
+            case SIM_ERR:
+                return SmsManager.RESULT_RIL_SIM_ERROR;
+            case INVALID_SIM_STATE:
+                return SmsManager.RESULT_RIL_INVALID_SIM_STATE;
+            case NO_SMS_TO_ACK:
+                return SmsManager.RESULT_RIL_NO_SMS_TO_ACK;
+            case SIM_BUSY:
+                return SmsManager.RESULT_RIL_SIM_BUSY;
+            case SIM_FULL:
+                return SmsManager.RESULT_RIL_SIM_FULL;
+            case NO_SUBSCRIPTION:
+                return SmsManager.RESULT_RIL_NO_SUBSCRIPTION;
+            case NO_NETWORK_FOUND:
+                return SmsManager.RESULT_RIL_NO_NETWORK_FOUND;
+            case DEVICE_IN_USE:
+                return SmsManager.RESULT_RIL_DEVICE_IN_USE;
+            case ABORTED:
+                return SmsManager.RESULT_RIL_ABORTED;
             default:
                 Rlog.d(TAG, "rilErrorToSmsManagerResult: " + rilError + " "
                         + SmsController.formatCrossStackMessageId(tracker.mMessageId));
@@ -1372,10 +1447,120 @@
      *                 Used for logging and diagnostics purposes. The id may be NULL.
      */
     public void sendText(String destAddr, String scAddr, String text,
+            PendingIntent sentIntent, PendingIntent deliveryIntent, Uri messageUri,
+            String callingPkg, boolean persistMessage, int priority,
+            boolean expectMore, int validityPeriod, boolean isForVvm,
+            long messageId) {
+        sendText(destAddr, scAddr, text, sentIntent, deliveryIntent, messageUri, callingPkg,
+                persistMessage, priority, expectMore, validityPeriod, isForVvm, messageId, false);
+    }
+
+    /**
+     * Send a text based SMS.
+     *
+     * @param destAddr the address to send the message to
+     * @param scAddr is the service center address or null to use
+     *  the current default SMSC
+     * @param text the body of the message to send
+     * @param sentIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is successfully sent, or failed.
+     *  The result code will be <code>Activity.RESULT_OK<code> for success,
+     *  or one of these errors:<br>
+     *  <code>SmsManager.RESULT_ERROR_GENERIC_FAILURE</code><br>
+     *  <code>SmsManager.RESULT_ERROR_RADIO_OFF</code><br>
+     *  <code>SmsManager.RESULT_ERROR_NULL_PDU</code><br>
+     *  <code>SmsManager.RESULT_ERROR_NO_SERVICE</code><br>
+     *  <code>SmsManager.RESULT_ERROR_LIMIT_EXCEEDED</code><br>
+     *  <code>SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE</code><br>
+     *  <code>SmsManager.RESULT_ERROR_SHORT_CODE_NOT_ALLOWED</code><br>
+     *  <code>SmsManager.RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED</code><br>
+     *  <code>SmsManager.RESULT_RADIO_NOT_AVAILABLE</code><br>
+     *  <code>SmsManager.RESULT_NETWORK_REJECT</code><br>
+     *  <code>SmsManager.RESULT_INVALID_ARGUMENTS</code><br>
+     *  <code>SmsManager.RESULT_INVALID_STATE</code><br>
+     *  <code>SmsManager.RESULT_NO_MEMORY</code><br>
+     *  <code>SmsManager.RESULT_INVALID_SMS_FORMAT</code><br>
+     *  <code>SmsManager.RESULT_SYSTEM_ERROR</code><br>
+     *  <code>SmsManager.RESULT_MODEM_ERROR</code><br>
+     *  <code>SmsManager.RESULT_NETWORK_ERROR</code><br>
+     *  <code>SmsManager.RESULT_ENCODING_ERROR</code><br>
+     *  <code>SmsManager.RESULT_INVALID_SMSC_ADDRESS</code><br>
+     *  <code>SmsManager.RESULT_OPERATION_NOT_ALLOWED</code><br>
+     *  <code>SmsManager.RESULT_INTERNAL_ERROR</code><br>
+     *  <code>SmsManager.RESULT_NO_RESOURCES</code><br>
+     *  <code>SmsManager.RESULT_CANCELLED</code><br>
+     *  <code>SmsManager.RESULT_REQUEST_NOT_SUPPORTED</code><br>
+     *  <code>SmsManager.RESULT_NO_BLUETOOTH_SERVICE</code><br>
+     *  <code>SmsManager.RESULT_INVALID_BLUETOOTH_ADDRESS</code><br>
+     *  <code>SmsManager.RESULT_BLUETOOTH_DISCONNECTED</code><br>
+     *  <code>SmsManager.RESULT_UNEXPECTED_EVENT_STOP_SENDING</code><br>
+     *  <code>SmsManager.RESULT_SMS_BLOCKED_DURING_EMERGENCY</code><br>
+     *  <code>SmsManager.RESULT_SMS_SEND_RETRY_FAILED</code><br>
+     *  <code>SmsManager.RESULT_REMOTE_EXCEPTION</code><br>
+     *  <code>SmsManager.RESULT_NO_DEFAULT_SMS_APP</code><br>
+     *  <code>SmsManager.RESULT_RIL_RADIO_NOT_AVAILABLE</code><br>
+     *  <code>SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY</code><br>
+     *  <code>SmsManager.RESULT_RIL_NETWORK_REJECT</code><br>
+     *  <code>SmsManager.RESULT_RIL_INVALID_STATE</code><br>
+     *  <code>SmsManager.RESULT_RIL_INVALID_ARGUMENTS</code><br>
+     *  <code>SmsManager.RESULT_RIL_NO_MEMORY</code><br>
+     *  <code>SmsManager.RESULT_RIL_REQUEST_RATE_LIMITED</code><br>
+     *  <code>SmsManager.RESULT_RIL_INVALID_SMS_FORMAT</code><br>
+     *  <code>SmsManager.RESULT_RIL_SYSTEM_ERR</code><br>
+     *  <code>SmsManager.RESULT_RIL_ENCODING_ERR</code><br>
+     *  <code>SmsManager.RESULT_RIL_INVALID_SMSC_ADDRESS</code><br>
+     *  <code>SmsManager.RESULT_RIL_MODEM_ERR</code><br>
+     *  <code>SmsManager.RESULT_RIL_NETWORK_ERR</code><br>
+     *  <code>SmsManager.RESULT_RIL_INTERNAL_ERR</code><br>
+     *  <code>SmsManager.RESULT_RIL_REQUEST_NOT_SUPPORTED</code><br>
+     *  <code>SmsManager.RESULT_RIL_INVALID_MODEM_STATE</code><br>
+     *  <code>SmsManager.RESULT_RIL_NETWORK_NOT_READY</code><br>
+     *  <code>SmsManager.RESULT_RIL_OPERATION_NOT_ALLOWED</code><br>
+     *  <code>SmsManager.RESULT_RIL_NO_RESOURCES</code><br>
+     *  <code>SmsManager.RESULT_RIL_CANCELLED</code><br>
+     *  <code>SmsManager.RESULT_RIL_SIM_ABSENT</code><br>
+     *  <code>SmsManager.RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED</code><br>
+     *  <code>SmsManager.RESULT_RIL_ACCESS_BARRED</code><br>
+     *  <code>SmsManager.RESULT_RIL_BLOCKED_DUE_TO_CALL</code><br>
+     *  For <code>SmsManager.RESULT_ERROR_GENERIC_FAILURE</code> or any of the RESULT_RIL errors,
+     *  the sentIntent may include the extra "errorCode" containing a radio technology specific
+     *  value, generally only useful for troubleshooting.<br>
+     *  The per-application based SMS control checks sentIntent. If sentIntent
+     *  is NULL the caller will be checked against all unknown applications,
+     *  which cause smaller number of SMS to be sent in checking period.
+     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is delivered to the recipient.  The
+     * @param messageUri optional URI of the message if it is already stored in the system
+     * @param callingPkg the calling package name
+     * @param persistMessage whether to save the sent message into SMS DB for a
+     *  non-default SMS app.
+     *
+     * @param priority Priority level of the message
+     *  Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
+     *  ---------------------------------
+     *  PRIORITY      | Level of Priority
+     *  ---------------------------------
+     *      '00'      |     Normal
+     *      '01'      |     Interactive
+     *      '10'      |     Urgent
+     *      '11'      |     Emergency
+     *  ----------------------------------
+     *  Any Other values included Negative considered as Invalid Priority Indicator of the message.
+     * @param expectMore is a boolean to indicate the sending messages through same link or not.
+     * @param validityPeriod Validity Period of the message in mins.
+     *  Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+     *  Validity Period(Minimum) -> 5 mins
+     *  Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
+     *  Any Other values included Negative considered as Invalid Validity Period of the message.
+     * @param messageId An id that uniquely identifies the message requested to be sent.
+     *                 Used for logging and diagnostics purposes. The id may be NULL.
+     * @param skipShortCodeCheck Skip check for short code type destination address.
+     */
+    public void sendText(String destAddr, String scAddr, String text,
                          PendingIntent sentIntent, PendingIntent deliveryIntent, Uri messageUri,
                          String callingPkg, boolean persistMessage, int priority,
                          boolean expectMore, int validityPeriod, boolean isForVvm,
-                         long messageId) {
+                         long messageId, boolean skipShortCodeCheck) {
         Rlog.d(TAG, "sendText id: " + SmsController.formatCrossStackMessageId(messageId));
         int messageRef = nextMessageRef();
         SmsMessageBase.SubmitPduBase pdu = getSubmitPdu(
@@ -1385,7 +1570,8 @@
             HashMap map = getSmsTrackerMap(destAddr, scAddr, text, pdu);
             SmsTracker tracker = getSmsTracker(callingPkg, map, sentIntent, deliveryIntent,
                     getFormat(), messageUri, expectMore, text, true /*isText*/,
-                    persistMessage, priority, validityPeriod, isForVvm, messageId, messageRef);
+                    persistMessage, priority, validityPeriod, isForVvm, messageId, messageRef,
+                    skipShortCodeCheck);
 
             if (!sendSmsByCarrierApp(false /* isDataSms */, tracker)) {
                 sendSubmitPdu(tracker);
@@ -1640,12 +1826,10 @@
 
         String carrierPackage = getCarrierAppPackageName();
         if (carrierPackage != null) {
-            Rlog.d(TAG, "Found carrier package " + carrierPackage
-                    + " "
+            Rlog.d(TAG, "Found carrier package " + carrierPackage + " "
                     + SmsController.formatCrossStackMessageId(getMultiTrackermessageId(trackers)));
             MultipartSmsSender smsSender = new MultipartSmsSender(parts, trackers);
-            smsSender.sendSmsByCarrierApp(carrierPackage,
-                    new MultipartSmsSenderCallback(smsSender));
+            smsSender.sendSmsByCarrierApp(carrierPackage, new SmsSenderCallback(smsSender));
         } else {
             Rlog.v(TAG, "No carrier package. "
                     + SmsController.formatCrossStackMessageId(getMultiTrackermessageId(trackers)));
@@ -1698,7 +1882,7 @@
                         getFormat(), unsentPartCount, anyPartFailed, messageUri, smsHeader,
                         (!lastPart || expectMore), fullMessageText, true /*isText*/,
                         true /*persistMessage*/, priority, validityPeriod, false /* isForVvm */,
-                        messageId, messageRef);
+                        messageId, messageRef, false);
             } else {
                 Rlog.e(TAG, "CdmaSMSDispatcher.getNewSubmitPduTracker(): getSubmitPdu() returned "
                         + "null " + SmsController.formatCrossStackMessageId(messageId));
@@ -1711,13 +1895,12 @@
                             SmsHeader.toByteArray(smsHeader), encoding, smsHeader.languageTable,
                             smsHeader.languageShiftTable, validityPeriod, messageRef);
             if (pdu != null) {
-                HashMap map =  getSmsTrackerMap(destinationAddress, scAddress,
-                        message, pdu);
+                HashMap map = getSmsTrackerMap(destinationAddress, scAddress, message, pdu);
                 return getSmsTracker(callingPackage, map, sentIntent,
                         deliveryIntent, getFormat(), unsentPartCount, anyPartFailed, messageUri,
                         smsHeader, (!lastPart || expectMore), fullMessageText, true /*isText*/,
                         false /*persistMessage*/, priority, validityPeriod, false /* isForVvm */,
-                        messageId, messageRef);
+                        messageId, messageRef, false);
             } else {
                 Rlog.e(TAG, "GsmSMSDispatcher.getNewSubmitPduTracker(): getSubmitPdu() returned "
                         + "null " + SmsController.formatCrossStackMessageId(messageId));
@@ -1875,7 +2058,8 @@
      */
     boolean checkDestination(SmsTracker[] trackers) {
         if (mContext.checkCallingOrSelfPermission(SEND_SMS_NO_CONFIRMATION)
-                == PackageManager.PERMISSION_GRANTED || trackers[0].mIsForVvm) {
+                == PackageManager.PERMISSION_GRANTED || trackers[0].mIsForVvm
+                || trackers[0].mSkipShortCodeDestAddrCheck) {
             return true;            // app is pre-approved to send to short codes
         } else {
             int rule = mPremiumSmsRule.get();
@@ -1916,6 +2100,12 @@
                                                 trackers[0].mDestAddress, networkCountryIso));
             }
 
+            if (smsCategory != SmsManager.SMS_CATEGORY_NOT_SHORT_CODE) {
+                int xmlVersion = mSmsDispatchersController.getUsageMonitor()
+                        .getShortCodeXmlFileVersion();
+                mPhone.getSmsStats().onOutgoingShortCodeSms(smsCategory, xmlVersion);
+            }
+
             if (smsCategory == SmsManager.SMS_CATEGORY_NOT_SHORT_CODE
                     || smsCategory == SmsManager.SMS_CATEGORY_FREE_SHORT_CODE
                     || smsCategory == SmsManager.SMS_CATEGORY_STANDARD_SHORT_CODE) {
@@ -2213,6 +2403,7 @@
         private Boolean mIsFromDefaultSmsApplication;
 
         private int mCarrierId;
+        private boolean mSkipShortCodeDestAddrCheck;
         // SMS anomaly uuid -- unexpected error from RIL
         private final UUID mAnomalyUnexpectedErrorFromRilUUID =
                 UUID.fromString("43043600-ea7a-44d2-9ae6-a58567ac7886");
@@ -2223,7 +2414,7 @@
                 SmsHeader smsHeader, boolean expectMore, String fullMessageText, int subId,
                 boolean isText, boolean persistMessage, int userId, int priority,
                 int validityPeriod, boolean isForVvm, long messageId, int carrierId,
-                int messageRef) {
+                int messageRef, boolean skipShortCodeDestAddrCheck) {
             mData = data;
             mSentIntent = sentIntent;
             mDeliveryIntent = deliveryIntent;
@@ -2249,6 +2440,7 @@
             mIsForVvm = isForVvm;
             mMessageId = messageId;
             mCarrierId = carrierId;
+            mSkipShortCodeDestAddrCheck = skipShortCodeDestAddrCheck;
         }
 
         public HashMap<String, Object> getData() {
@@ -2263,12 +2455,21 @@
             return mAppInfo != null ? mAppInfo.packageName : null;
         }
 
+        /**
+         * Get the calling Application Info
+         * @return Application Info
+         */
+        public ApplicationInfo getAppInfo() {
+            return mAppInfo == null ? null : mAppInfo.applicationInfo;
+        }
+
         /** Return if the SMS was originated from the default SMS application. */
         public boolean isFromDefaultSmsApplication(Context context) {
             if (mIsFromDefaultSmsApplication == null) {
+                UserHandle userHandle = TelephonyUtils.getSubscriptionUserHandle(context, mSubId);
                 // Perform a lazy initialization, due to the cost of the operation.
-                mIsFromDefaultSmsApplication =
-                        SmsApplication.isDefaultSmsApplication(context, getAppPackageName());
+                mIsFromDefaultSmsApplication = SmsApplication.isDefaultSmsApplicationAsUser(context,
+                                    getAppPackageName(), userHandle);
             }
             return mIsFromDefaultSmsApplication;
         }
@@ -2516,7 +2717,7 @@
             AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri,
             SmsHeader smsHeader, boolean expectMore, String fullMessageText, boolean isText,
             boolean persistMessage, int priority, int validityPeriod, boolean isForVvm,
-            long messageId, int messageRef) {
+            long messageId, int messageRef, boolean skipShortCodeCheck) {
         // Get package info via packagemanager
         UserHandle callingUser = UserHandle.getUserHandleForUid(Binder.getCallingUid());
         final int userId = callingUser.getIdentifier();
@@ -2533,7 +2734,8 @@
         return new SmsTracker(data, sentIntent, deliveryIntent, appInfo, destAddr, format,
                 unsentPartCount, anyPartFailed, messageUri, smsHeader, expectMore,
                 fullMessageText, getSubId(), isText, persistMessage, userId, priority,
-                validityPeriod, isForVvm, messageId, mPhone.getCarrierId(), messageRef);
+                validityPeriod, isForVvm, messageId, mPhone.getCarrierId(), messageRef,
+                skipShortCodeCheck);
     }
 
     protected SmsTracker getSmsTracker(String callingPackage, HashMap<String, Object> data,
@@ -2544,17 +2746,18 @@
                 null/*unsentPartCount*/, null/*anyPartFailed*/, messageUri, null/*smsHeader*/,
                 expectMore, fullMessageText, isText, persistMessage,
                 SMS_MESSAGE_PRIORITY_NOT_SPECIFIED, SMS_MESSAGE_PERIOD_NOT_SPECIFIED, isForVvm,
-                messageId, messageRef);
+                messageId, messageRef, false);
     }
 
     protected SmsTracker getSmsTracker(String callingPackage, HashMap<String, Object> data,
             PendingIntent sentIntent, PendingIntent deliveryIntent, String format, Uri messageUri,
             boolean expectMore, String fullMessageText, boolean isText, boolean persistMessage,
-            int priority, int validityPeriod, boolean isForVvm, long messageId, int messageRef) {
+            int priority, int validityPeriod, boolean isForVvm, long messageId, int messageRef,
+            boolean skipShortCodeCheck) {
         return getSmsTracker(callingPackage, data, sentIntent, deliveryIntent, format,
                 null/*unsentPartCount*/, null/*anyPartFailed*/, messageUri, null/*smsHeader*/,
                 expectMore, fullMessageText, isText, persistMessage, priority, validityPeriod,
-                isForVvm, messageId, messageRef);
+                isForVvm, messageId, messageRef, skipShortCodeCheck);
     }
 
     protected HashMap<String, Object> getSmsTrackerMap(String destAddr, String scAddr,
@@ -2773,10 +2976,17 @@
         IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
         pw.println(TAG);
         pw.increaseIndent();
+
         pw.println("mLocalLog:");
         pw.increaseIndent();
         mLocalLog.dump(fd, pw, args);
         pw.decreaseIndent();
+
+        pw.println("mSmsOutgoingErrorCodes:");
+        pw.increaseIndent();
+        mSmsOutgoingErrorCodes.dump(fd, pw, args);
+        pw.decreaseIndent();
+
         pw.decreaseIndent();
     }
 }
diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java
old mode 100755
new mode 100644
index 69f4da7..29b1aca
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -71,7 +71,6 @@
 import android.telephony.RadioAccessFamily;
 import android.telephony.ServiceState;
 import android.telephony.ServiceState.RilRadioTechnology;
-import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
 import android.telephony.TelephonyManager;
@@ -92,6 +91,7 @@
 import com.android.internal.telephony.cdnr.CarrierDisplayNameData;
 import com.android.internal.telephony.cdnr.CarrierDisplayNameResolver;
 import com.android.internal.telephony.data.AccessNetworksManager;
+import com.android.internal.telephony.data.AccessNetworksManager.AccessNetworksManagerCallback;
 import com.android.internal.telephony.data.DataNetwork;
 import com.android.internal.telephony.data.DataNetworkController.DataNetworkControllerCallback;
 import com.android.internal.telephony.imsphone.ImsPhone;
@@ -180,6 +180,7 @@
     private long mLastCellInfoReqTime;
     private List<CellInfo> mLastCellInfoList = null;
     private List<PhysicalChannelConfig> mLastPhysicalChannelConfigList = null;
+    private int mLastAnchorNrCellId = PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN;
 
     private final Set<Integer> mRadioPowerOffReasons = new HashSet();
 
@@ -217,7 +218,6 @@
     private RegistrantList mNrStateChangedRegistrants = new RegistrantList();
     private RegistrantList mNrFrequencyChangedRegistrants = new RegistrantList();
     private RegistrantList mCssIndicatorChangedRegistrants = new RegistrantList();
-    private final RegistrantList mBandwidthChangedRegistrants = new RegistrantList();
     private final RegistrantList mAirplaneModeChangedRegistrants = new RegistrantList();
     private final RegistrantList mAreaCodeChangedRegistrants = new RegistrantList();
 
@@ -321,14 +321,10 @@
     private CarrierDisplayNameResolver mCdnr;
 
     private boolean mImsRegistrationOnOff = false;
-    /** Radio is disabled by carrier. Radio power will not be override if this field is set */
-    private boolean mRadioDisabledByCarrier = false;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private boolean mDeviceShuttingDown = false;
     /** Keep track of SPN display rules, so we only broadcast intent if something changes. */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private boolean mSpnUpdatePending = false;
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private String mCurSpn = null;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private String mCurDataSpn = null;
@@ -347,8 +343,6 @@
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private SubscriptionManager mSubscriptionManager;
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private SubscriptionController mSubscriptionController;
     private SubscriptionManagerService mSubscriptionManagerService;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private final SstSubscriptionsChangedListener mOnSubscriptionsChangedListener =
@@ -386,7 +380,6 @@
             // If not, then the subId has changed, so we need to remember the old subId,
             // even if the new subId is invalid (likely).
             mPrevSubId = mSubId;
-            mSubId = curSubId;
 
             // Update voicemail count and notify message waiting changed regardless of
             // whether the new subId is valid. This is an exception to the general logic
@@ -395,72 +388,64 @@
             // which seems desirable.
             mPhone.updateVoiceMail();
 
-            if (!SubscriptionManager.isValidSubscriptionId(mSubId)) {
+            if (!SubscriptionManager.isValidSubscriptionId(curSubId)) {
                 if (SubscriptionManager.isValidSubscriptionId(mPrevSubId)) {
                     // just went from valid to invalid subId, so notify phone state listeners
                     // with final broadcast
                     mPhone.notifyServiceStateChangedForSubId(mOutOfServiceSS,
                             ServiceStateTracker.this.mPrevSubId);
                 }
-                // If the new subscription ID isn't valid, then we don't need to do all the
-                // UI updating, so we're done.
-                return;
+            } else {
+                Context context = mPhone.getContext();
+
+                mPhone.notifyPhoneStateChanged();
+
+                if (!SubscriptionManager.isValidSubscriptionId(mPrevSubId)) {
+                    // just went from invalid to valid subId, so notify with current service
+                    // state in case our service state was never broadcasted (we don't notify
+                    // service states when the subId is invalid)
+                    mPhone.notifyServiceStateChanged(mPhone.getServiceState());
+                }
+
+                boolean restoreSelection = !context.getResources().getBoolean(
+                        com.android.internal.R.bool.skip_restoring_network_selection);
+                mPhone.sendSubscriptionSettings(restoreSelection);
+
+                setDataNetworkTypeForPhone(mSS.getRilDataRadioTechnology());
+
+                // Remove old network selection sharedPreferences since SP key names are now
+                // changed to include subId. This will be done only once when upgrading from an
+                // older build that did not include subId in the names.
+                SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(
+                        context);
+                String oldNetworkSelection = sp.getString(
+                        Phone.NETWORK_SELECTION_KEY, "");
+                String oldNetworkSelectionName = sp.getString(
+                        Phone.NETWORK_SELECTION_NAME_KEY, "");
+                String oldNetworkSelectionShort = sp.getString(
+                        Phone.NETWORK_SELECTION_SHORT_KEY, "");
+                if (!TextUtils.isEmpty(oldNetworkSelection)
+                        || !TextUtils.isEmpty(oldNetworkSelectionName)
+                        || !TextUtils.isEmpty(oldNetworkSelectionShort)) {
+                    SharedPreferences.Editor editor = sp.edit();
+                    editor.putString(Phone.NETWORK_SELECTION_KEY + curSubId,
+                            oldNetworkSelection);
+                    editor.putString(Phone.NETWORK_SELECTION_NAME_KEY + curSubId,
+                            oldNetworkSelectionName);
+                    editor.putString(Phone.NETWORK_SELECTION_SHORT_KEY + curSubId,
+                            oldNetworkSelectionShort);
+                    editor.remove(Phone.NETWORK_SELECTION_KEY);
+                    editor.remove(Phone.NETWORK_SELECTION_NAME_KEY);
+                    editor.remove(Phone.NETWORK_SELECTION_SHORT_KEY);
+                    editor.commit();
+                }
+
+                // Once sub id becomes valid, we need to update the service provider name
+                // displayed on the UI again. The old SPN update intents sent to
+                // MobileSignalController earlier were actually ignored due to invalid sub id.
+                updateSpnDisplay();
             }
-
-            Context context = mPhone.getContext();
-
-            mPhone.notifyPhoneStateChanged();
-
-            if (!SubscriptionManager.isValidSubscriptionId(mPrevSubId)) {
-                // just went from invalid to valid subId, so notify with current service
-                // state in case our service state was never broadcasted (we don't notify
-                // service states when the subId is invalid)
-                mPhone.notifyServiceStateChanged(mPhone.getServiceState());
-            }
-
-            boolean restoreSelection = !context.getResources().getBoolean(
-                    com.android.internal.R.bool.skip_restoring_network_selection);
-            mPhone.sendSubscriptionSettings(restoreSelection);
-
-            setDataNetworkTypeForPhone(mSS.getRilDataRadioTechnology());
-
-            if (mSpnUpdatePending) {
-                mSubscriptionController.setPlmnSpn(mPhone.getPhoneId(), mCurShowPlmn,
-                        mCurPlmn, mCurShowSpn, mCurSpn);
-                mSpnUpdatePending = false;
-            }
-
-            // Remove old network selection sharedPreferences since SP key names are now
-            // changed to include subId. This will be done only once when upgrading from an
-            // older build that did not include subId in the names.
-            SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(
-                    context);
-            String oldNetworkSelection = sp.getString(
-                    Phone.NETWORK_SELECTION_KEY, "");
-            String oldNetworkSelectionName = sp.getString(
-                    Phone.NETWORK_SELECTION_NAME_KEY, "");
-            String oldNetworkSelectionShort = sp.getString(
-                    Phone.NETWORK_SELECTION_SHORT_KEY, "");
-            if (!TextUtils.isEmpty(oldNetworkSelection)
-                    || !TextUtils.isEmpty(oldNetworkSelectionName)
-                    || !TextUtils.isEmpty(oldNetworkSelectionShort)) {
-                SharedPreferences.Editor editor = sp.edit();
-                editor.putString(Phone.NETWORK_SELECTION_KEY + mSubId,
-                        oldNetworkSelection);
-                editor.putString(Phone.NETWORK_SELECTION_NAME_KEY + mSubId,
-                        oldNetworkSelectionName);
-                editor.putString(Phone.NETWORK_SELECTION_SHORT_KEY + mSubId,
-                        oldNetworkSelectionShort);
-                editor.remove(Phone.NETWORK_SELECTION_KEY);
-                editor.remove(Phone.NETWORK_SELECTION_NAME_KEY);
-                editor.remove(Phone.NETWORK_SELECTION_SHORT_KEY);
-                editor.commit();
-            }
-
-            // Once sub id becomes valid, we need to update the service provider name
-            // displayed on the UI again. The old SPN update intents sent to
-            // MobileSignalController earlier were actually ignored due to invalid sub id.
-            updateSpnDisplay();
+            mSubId = curSubId;
         }
     };
 
@@ -560,8 +545,12 @@
         public void onReceive(Context context, Intent intent) {
             final String action = intent.getAction();
             if (action.equals(Intent.ACTION_LOCALE_CHANGED)) {
+                log("ACTION_LOCALE_CHANGED");
                 // Update emergency string or operator name, polling service state.
                 pollState();
+                // Depends on modem, ServiceState is not necessarily updated, so make sure updating
+                // SPN.
+                updateSpnDisplay();
             } else if (action.equals(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED)) {
                 String lastKnownNetworkCountry = intent.getStringExtra(
                         TelephonyManager.EXTRA_LAST_KNOWN_NETWORK_COUNTRY);
@@ -623,6 +612,12 @@
      */
     private DataNetworkControllerCallback mDataDisconnectedCallback;
 
+    /**
+     * AccessNetworksManagerCallback is used for preferred on the IWLAN when preferred transport
+     * type changed in AccessNetworksManager.
+     */
+    private AccessNetworksManagerCallback mAccessNetworksManagerCallback = null;
+
     public ServiceStateTracker(GsmCdmaPhone phone, CommandsInterface ci) {
         mNitzState = TelephonyComponentFactory.getInstance()
                 .inject(NitzStateMachine.class.getName())
@@ -652,12 +647,7 @@
         mCi.registerForCellInfoList(this, EVENT_UNSOL_CELL_INFO_LIST, null);
         mCi.registerForPhysicalChannelConfiguration(this, EVENT_PHYSICAL_CHANNEL_CONFIG, null);
 
-        if (mPhone.isSubscriptionManagerServiceEnabled()) {
-            mSubscriptionManagerService = SubscriptionManagerService.getInstance();
-        } else {
-            mSubscriptionController = SubscriptionController.getInstance();
-        }
-
+        mSubscriptionManagerService = SubscriptionManagerService.getInstance();
         mSubscriptionManager = SubscriptionManager.from(phone.getContext());
         mSubscriptionManager.addOnSubscriptionsChangedListener(
                 new android.os.HandlerExecutor(this), mOnSubscriptionsChangedListener);
@@ -670,7 +660,7 @@
 
         mAccessNetworksManager = mPhone.getAccessNetworksManager();
         mOutOfServiceSS = new ServiceState();
-        mOutOfServiceSS.setOutOfService(mAccessNetworksManager.isInLegacyMode(), false);
+        mOutOfServiceSS.setOutOfService(false);
 
         for (int transportType : mAccessNetworksManager.getAvailableTransports()) {
             mRegStateManagers.append(transportType, new NetworkRegistrationManager(
@@ -693,7 +683,7 @@
                 Settings.Global.ENABLE_CELLULAR_ON_BOOT, 1);
         mDesiredPowerState = (enableCellularOnBoot > 0) && ! (airplaneMode > 0);
         if (!mDesiredPowerState) {
-            mRadioPowerOffReasons.add(Phone.RADIO_POWER_REASON_USER);
+            mRadioPowerOffReasons.add(TelephonyManager.RADIO_POWER_REASON_USER);
         }
         mRadioPowerLog.log("init : airplane mode = " + airplaneMode + " enableCellularOnBoot = " +
                 enableCellularOnBoot);
@@ -735,6 +725,23 @@
                 }
             }
         };
+
+        mAccessNetworksManagerCallback = new AccessNetworksManagerCallback(this::post) {
+            @Override
+            public void onPreferredTransportChanged(int networkCapability) {
+                // Check if preferred on IWLAN was changed in ServiceState.
+                boolean isIwlanPreferred = mAccessNetworksManager.isAnyApnOnIwlan();
+                if (mSS.isIwlanPreferred() != isIwlanPreferred) {
+                    log("onPreferredTransportChanged: IwlanPreferred is changed to "
+                            + isIwlanPreferred);
+                    mSS.setIwlanPreferred(isIwlanPreferred);
+                    mPhone.notifyServiceStateChanged(mPhone.getServiceState());
+                }
+            }
+        };
+        if (mAccessNetworksManagerCallback != null) {
+            mAccessNetworksManager.registerCallback(mAccessNetworksManagerCallback);
+        }
     }
 
     @VisibleForTesting
@@ -770,9 +777,9 @@
         }
 
         mSS = new ServiceState();
-        mSS.setOutOfService(mAccessNetworksManager.isInLegacyMode(), false);
+        mSS.setOutOfService(false);
         mNewSS = new ServiceState();
-        mNewSS.setOutOfService(mAccessNetworksManager.isInLegacyMode(), false);
+        mNewSS.setOutOfService(false);
         mLastCellInfoReqTime = 0;
         mLastCellInfoList = null;
         mStartedGprsRegCheck = false;
@@ -873,13 +880,20 @@
             mCSST.dispose();
             mCSST = null;
         }
+        if (mAccessNetworksManagerCallback != null) {
+            mAccessNetworksManager.unregisterCallback(mAccessNetworksManagerCallback);
+            mAccessNetworksManagerCallback = null;
+        }
     }
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public boolean getDesiredPowerState() {
         return mDesiredPowerState;
     }
-    public boolean getPowerStateFromCarrier() { return !mRadioDisabledByCarrier; }
+
+    public boolean getPowerStateFromCarrier() {
+        return !mRadioPowerOffReasons.contains(TelephonyManager.RADIO_POWER_REASON_CARRIER);
+    }
 
     public List<PhysicalChannelConfig> getPhysicalChannelConfigList() {
         return mLastPhysicalChannelConfigList;
@@ -1069,7 +1083,7 @@
      * @return the current reasons for which the radio is off.
      */
     public Set<Integer> getRadioPowerOffReasons() {
-        return mRadioPowerOffReasons;
+        return Set.copyOf(mRadioPowerOffReasons);
     }
 
     /**
@@ -1095,7 +1109,7 @@
     public void setRadioPower(boolean power, boolean forEmergencyCall,
             boolean isSelectedPhoneForEmergencyCall, boolean forceApply) {
         setRadioPowerForReason(power, forEmergencyCall, isSelectedPhoneForEmergencyCall, forceApply,
-                Phone.RADIO_POWER_REASON_USER);
+                TelephonyManager.RADIO_POWER_REASON_USER);
     }
 
     /**
@@ -1136,23 +1150,6 @@
     }
 
     /**
-     * Radio power set from carrier action. if set to false means carrier desire to turn radio off
-     * and radio wont be re-enabled unless carrier explicitly turn it back on.
-     * @param enable indicate if radio power is enabled or disabled from carrier action.
-     */
-    public void setRadioPowerFromCarrier(boolean enable) {
-        boolean disableByCarrier = !enable;
-        if (mRadioDisabledByCarrier == disableByCarrier) {
-            log("setRadioPowerFromCarrier mRadioDisabledByCarrier is already "
-                    + disableByCarrier + " Do nothing.");
-            return;
-        }
-
-        mRadioDisabledByCarrier = disableByCarrier;
-        setPowerStateToDesired();
-    }
-
-    /**
      * These two flags manage the behavior of the cell lock -- the
      * lock should be held if either flag is true.  The intention is
      * to allow temporary acquisition of the lock to get a single
@@ -1651,7 +1648,8 @@
                 if (ar.exception == null) {
                     boolean enable = (boolean) ar.result;
                     if (DBG) log("EVENT_RADIO_POWER_FROM_CARRIER: " + enable);
-                    setRadioPowerFromCarrier(enable);
+                    setRadioPowerForReason(enable, false, false, false,
+                            TelephonyManager.RADIO_POWER_REASON_CARRIER);
                 }
                 break;
 
@@ -1660,8 +1658,20 @@
                 if (ar.exception == null) {
                     List<PhysicalChannelConfig> list = (List<PhysicalChannelConfig>) ar.result;
                     if (VDBG) {
-                        log("EVENT_PHYSICAL_CHANNEL_CONFIG: size=" + list.size() + " list="
-                                + list);
+                        log("EVENT_PHYSICAL_CHANNEL_CONFIG: list=" + list
+                                + (list == null ? "" : ", list.size()=" + list.size()));
+                    }
+                    if ((list == null || list.isEmpty())
+                            && mLastAnchorNrCellId != PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN
+                            && mPhone.getContext().getSystemService(TelephonyManager.class)
+                                    .isRadioInterfaceCapabilitySupported(TelephonyManager
+                                            .CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED)
+                            && !mCarrierConfig.getBoolean(CarrierConfigManager
+                                    .KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL)
+                            && mCarrierConfig.getBoolean(CarrierConfigManager
+                                    .KEY_RATCHET_NR_ADVANCED_BANDWIDTH_IF_RRC_IDLE_BOOL)) {
+                        log("Ignore empty PCC list when RRC idle.");
+                        break;
                     }
                     mLastPhysicalChannelConfigList = list;
                     boolean hasChanged = false;
@@ -1673,13 +1683,40 @@
                         mNrFrequencyChangedRegistrants.notifyRegistrants();
                         hasChanged = true;
                     }
-                    hasChanged |= RatRatcheter
-                            .updateBandwidths(getBandwidthsFromConfigs(list), mSS);
+                    int anchorNrCellId = PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN;
+                    if (list != null) {
+                        anchorNrCellId = list
+                                .stream()
+                                .filter(config -> config.getNetworkType()
+                                        == TelephonyManager.NETWORK_TYPE_NR
+                                        && config.getConnectionStatus()
+                                        == PhysicalChannelConfig.CONNECTION_PRIMARY_SERVING)
+                                .map(PhysicalChannelConfig::getPhysicalCellId)
+                                .findFirst()
+                                .orElse(PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN);
+                    }
+                    int[] bandwidths = new int[0];
+                    if (list != null) {
+                        bandwidths = getBandwidthsFromLastPhysicalChannelConfigs();
+                    }
+                    if (anchorNrCellId == mLastAnchorNrCellId
+                            && anchorNrCellId != PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN) {
+                        log("Ratchet bandwidths since anchor NR cell is the same.");
+                        hasChanged |= RatRatcheter.updateBandwidths(bandwidths, mSS);
+                    } else {
+                        log("Do not ratchet bandwidths since anchor NR cell is different ("
+                                + mLastAnchorNrCellId + " -> " + anchorNrCellId
+                                + "). New bandwidths are " + Arrays.toString(bandwidths));
+                        mLastAnchorNrCellId = anchorNrCellId;
+                        hasChanged |= !Arrays.equals(mSS.getCellBandwidths(), bandwidths);
+                        mSS.setCellBandwidths(bandwidths);
+                    }
 
                     mPhone.notifyPhysicalChannelConfig(list);
                     // Notify NR frequency, NR connection status or bandwidths changed.
                     if (hasChanged) {
                         mPhone.notifyServiceStateChanged(mPhone.getServiceState());
+                        mServiceStateChangedRegistrants.notifyRegistrants();
                         TelephonyMetrics.getInstance().writeServiceStateChanged(
                                 mPhone.getPhoneId(), mSS);
                         mPhone.getVoiceCallSessionStats().onServiceStateChanged(mSS);
@@ -1753,8 +1790,12 @@
         return simAbsent;
     }
 
-    private static int[] getBandwidthsFromConfigs(List<PhysicalChannelConfig> list) {
-        return list.stream()
+    private int[] getBandwidthsFromLastPhysicalChannelConfigs() {
+        boolean includeLte = mCarrierConfig.getBoolean(
+                CarrierConfigManager.KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL);
+        return mLastPhysicalChannelConfigList.stream()
+                .filter(config -> includeLte
+                        || config.getNetworkType() == TelephonyManager.NETWORK_TYPE_NR)
                 .map(PhysicalChannelConfig::getCellBandwidthDownlinkKhz)
                 .mapToInt(Integer::intValue)
                 .toArray();
@@ -2091,16 +2132,20 @@
         if (physicalChannelConfigs != null) {
             for (PhysicalChannelConfig config : physicalChannelConfigs) {
                 if (isNrPhysicalChannelConfig(config) && isInternetPhysicalChannelConfig(config)) {
-                    // Update the NR frequency range if there is an internet data connection
+                    // Update the NR frequency range if there is an active internet data connection
                     // associated with this NR physical channel channel config.
-                    newFrequencyRange = ServiceState.getBetterNRFrequencyRange(
-                            newFrequencyRange, config.getFrequencyRange());
-                    break;
+                    // If there are multiple valid configs, use the highest frequency range value.
+                    newFrequencyRange = Math.max(newFrequencyRange, config.getFrequencyRange());
                 }
             }
         }
 
         boolean hasChanged = newFrequencyRange != ss.getNrFrequencyRange();
+        if (hasChanged) {
+            log(String.format("NR frequency range changed from %s to %s.",
+                    ServiceState.frequencyRangeToString(ss.getNrFrequencyRange()),
+                    ServiceState.frequencyRangeToString(newFrequencyRange)));
+        }
         ss.setNrFrequencyRange(newFrequencyRange);
         return hasChanged;
     }
@@ -2131,6 +2176,11 @@
         }
 
         boolean hasChanged = newNrState != oldNrState;
+        if (hasChanged) {
+            log(String.format("NR state changed from %s to %s.",
+                    NetworkRegistrationInfo.nrStateToString(oldNrState),
+                    NetworkRegistrationInfo.nrStateToString(newNrState)));
+        }
         regInfo.setNrState(newNrState);
         ss.addNetworkRegistrationInfo(regInfo);
         return hasChanged;
@@ -2505,7 +2555,7 @@
             // Prioritize the PhysicalChannelConfig list because we might already be in carrier
             // aggregation by the time poll state is performed.
             if (primaryPcc != null) {
-                bandwidths = getBandwidthsFromConfigs(mLastPhysicalChannelConfigList);
+                bandwidths = getBandwidthsFromLastPhysicalChannelConfigs();
                 for (int bw : bandwidths) {
                     if (!isValidLteBandwidthKhz(bw)) {
                         loge("Invalid LTE Bandwidth in RegistrationState, " + bw);
@@ -2541,7 +2591,7 @@
             // Prioritize the PhysicalChannelConfig list because we might already be in carrier
             // aggregation by the time poll state is performed.
             if (primaryPcc != null) {
-                bandwidths = getBandwidthsFromConfigs(mLastPhysicalChannelConfigList);
+                bandwidths = getBandwidthsFromLastPhysicalChannelConfigs();
                 for (int bw : bandwidths) {
                     if (!isValidNrBandwidthKhz(bw)) {
                         loge("Invalid NR Bandwidth in RegistrationState, " + bw);
@@ -2728,7 +2778,7 @@
 
     private void notifySpnDisplayUpdate(CarrierDisplayNameData data) {
         int subId = mPhone.getSubId();
-        // Update ACTION_SERVICE_PROVIDERS_UPDATED IFF any value changes
+        // Update ACTION_SERVICE_PROVIDERS_UPDATED if any value changes
         if (mSubId != subId
                 || data.shouldShowPlmn() != mCurShowPlmn
                 || data.shouldShowSpn() != mCurShowSpn
@@ -2758,22 +2808,12 @@
             SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
             mPhone.getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL);
 
-            if (mPhone.isSubscriptionManagerServiceEnabled()) {
-                if (SubscriptionManager.isValidSubscriptionId(subId)) {
-                    mSubscriptionManagerService.setCarrierName(subId, TextUtils.emptyIfNull(
-                            getCarrierName(data.shouldShowPlmn(), data.getPlmn(),
-                                    data.shouldShowSpn(), data.getSpn())));
-                }
-            } else {
-                if (!mSubscriptionController.setPlmnSpn(mPhone.getPhoneId(),
-                        data.shouldShowPlmn(), data.getPlmn(), data.shouldShowSpn(),
-                        data.getSpn())) {
-                    mSpnUpdatePending = true;
-                }
+            if (SubscriptionManager.isValidSubscriptionId(subId)) {
+                mSubscriptionManagerService.setCarrierName(subId, TextUtils.emptyIfNull(
+                        getCarrierName(data.shouldShowPlmn(), data.getPlmn(),
+                                data.shouldShowSpn(), data.getSpn())));
             }
         }
-
-        mSubId = subId;
         mCurShowSpn = data.shouldShowSpn();
         mCurShowPlmn = data.shouldShowPlmn();
         mCurSpn = data.getSpn();
@@ -3093,12 +3133,12 @@
     protected void setPowerStateToDesired(boolean forEmergencyCall,
             boolean isSelectedPhoneForEmergencyCall, boolean forceApply) {
         if (DBG) {
-            String tmpLog = "setPowerStateToDesired: mDeviceShuttingDown=" + mDeviceShuttingDown +
-                    ", mDesiredPowerState=" + mDesiredPowerState +
-                    ", getRadioState=" + mCi.getRadioState() +
-                    ", mRadioDisabledByCarrier=" + mRadioDisabledByCarrier +
-                    ", IMS reg state=" + mImsRegistrationOnOff +
-                    ", pending radio off=" + hasMessages(EVENT_POWER_OFF_RADIO_IMS_DEREG_TIMEOUT);
+            String tmpLog = "setPowerStateToDesired: mDeviceShuttingDown=" + mDeviceShuttingDown
+                    + ", mDesiredPowerState=" + mDesiredPowerState
+                    + ", getRadioState=" + mCi.getRadioState()
+                    + ", mRadioPowerOffReasons=" + mRadioPowerOffReasons
+                    + ", IMS reg state=" + mImsRegistrationOnOff
+                    + ", pending radio off=" + hasMessages(EVENT_POWER_OFF_RADIO_IMS_DEREG_TIMEOUT);
             log(tmpLog);
             mRadioPowerLog.log(tmpLog);
         }
@@ -3110,10 +3150,10 @@
         }
 
         // If we want it on and it's off, turn it on
-        if (mDesiredPowerState && !mRadioDisabledByCarrier
+        if (mDesiredPowerState && mRadioPowerOffReasons.isEmpty()
                 && (forceApply || mCi.getRadioState() == TelephonyManager.RADIO_POWER_OFF)) {
             mCi.setRadioPower(true, forEmergencyCall, isSelectedPhoneForEmergencyCall, null);
-        } else if ((!mDesiredPowerState || mRadioDisabledByCarrier) && mCi.getRadioState()
+        } else if ((!mDesiredPowerState || !mRadioPowerOffReasons.isEmpty()) && mCi.getRadioState()
                 == TelephonyManager.RADIO_POWER_ON) {
             if (DBG) log("setPowerStateToDesired: powerOffRadioSafely()");
             powerOffRadioSafely();
@@ -3260,7 +3300,6 @@
                 + " mImsRegistrationOnOff=" + mImsRegistrationOnOff
                 + "}");
 
-
         if (mImsRegistrationOnOff && !registered) {
             // moving to deregistered, only send this event if we need to re-evaluate
             if (getRadioPowerOffDelayTimeoutForImsRegistration() > 0) {
@@ -3273,6 +3312,9 @@
             }
         }
         mImsRegistrationOnOff = registered;
+
+        // It's possible ServiceState changes did not trigger SPN display update; we update it here.
+        updateSpnDisplay();
     }
 
     public void onImsCapabilityChanged() {
@@ -3309,7 +3351,7 @@
                 nri = mNewSS.getNetworkRegistrationInfo(
                         NetworkRegistrationInfo.DOMAIN_PS,
                         AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
-                mNewSS.setOutOfService(mAccessNetworksManager.isInLegacyMode(), false);
+                mNewSS.setOutOfService(false);
                 // Add the IWLAN registration info back to service state.
                 if (nri != null) {
                     mNewSS.addNetworkRegistrationInfo(nri);
@@ -3326,7 +3368,7 @@
                 nri = mNewSS.getNetworkRegistrationInfo(
                         NetworkRegistrationInfo.DOMAIN_PS,
                         AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
-                mNewSS.setOutOfService(mAccessNetworksManager.isInLegacyMode(), true);
+                mNewSS.setOutOfService(true);
                 // Add the IWLAN registration info back to service state.
                 if (nri != null) {
                     mNewSS.addNetworkRegistrationInfo(nri);
@@ -3421,8 +3463,8 @@
         updateNrFrequencyRangeFromPhysicalChannelConfigs(mLastPhysicalChannelConfigList, mNewSS);
         updateNrStateFromPhysicalChannelConfigs(mLastPhysicalChannelConfigList, mNewSS);
 
-        if (TelephonyUtils.IS_DEBUGGABLE && mPhone.mTelephonyTester != null) {
-            mPhone.mTelephonyTester.overrideServiceState(mNewSS);
+        if (TelephonyUtils.IS_DEBUGGABLE && mPhone.getTelephonyTester() != null) {
+            mPhone.getTelephonyTester().overrideServiceState(mNewSS);
         }
 
         NetworkRegistrationInfo networkRegState = mNewSS.getNetworkRegistrationInfo(
@@ -3453,14 +3495,10 @@
                 mSS.getState() == ServiceState.STATE_POWER_OFF
                         && mNewSS.getState() != ServiceState.STATE_POWER_OFF;
 
-        SparseBooleanArray hasDataAttached = new SparseBooleanArray(
-                mAccessNetworksManager.getAvailableTransports().length);
-        SparseBooleanArray hasDataDetached = new SparseBooleanArray(
-                mAccessNetworksManager.getAvailableTransports().length);
-        SparseBooleanArray hasRilDataRadioTechnologyChanged = new SparseBooleanArray(
-                mAccessNetworksManager.getAvailableTransports().length);
-        SparseBooleanArray hasDataRegStateChanged = new SparseBooleanArray(
-                mAccessNetworksManager.getAvailableTransports().length);
+        SparseBooleanArray hasDataAttached = new SparseBooleanArray();
+        SparseBooleanArray hasDataDetached = new SparseBooleanArray();
+        SparseBooleanArray hasRilDataRadioTechnologyChanged = new SparseBooleanArray();
+        SparseBooleanArray hasDataRegStateChanged = new SparseBooleanArray();
         boolean anyDataRegChanged = false;
         boolean anyDataRatChanged = false;
         boolean hasAlphaRawChanged =
@@ -3557,9 +3595,6 @@
 
         boolean hasCssIndicatorChanged = (mSS.getCssIndicator() != mNewSS.getCssIndicator());
 
-        boolean hasBandwidthChanged = !Arrays.equals(
-                mSS.getCellBandwidths(), mNewSS.getCellBandwidths());
-
         boolean has4gHandoff = false;
         boolean hasMultiApnSupport = false;
         boolean hasLostMultiApnSupport = false;
@@ -3603,7 +3638,6 @@
                     + " hasCssIndicatorChanged = " + hasCssIndicatorChanged
                     + " hasNrFrequencyRangeChanged = " + hasNrFrequencyRangeChanged
                     + " hasNrStateChanged = " + hasNrStateChanged
-                    + " hasBandwidthChanged = " + hasBandwidthChanged
                     + " hasAirplaneModeOnlChanged = " + hasAirplaneModeOnChanged);
         }
 
@@ -3650,7 +3684,7 @@
         ServiceState oldMergedSS = new ServiceState(mPhone.getServiceState());
         mSS = new ServiceState(mNewSS);
 
-        mNewSS.setOutOfService(mAccessNetworksManager.isInLegacyMode(), false);
+        mNewSS.setOutOfService(false);
 
         mCellIdentity = primaryCellIdentity;
         if (mSS.getState() == ServiceState.STATE_IN_SERVICE && primaryCellIdentity != null) {
@@ -3696,10 +3730,6 @@
             mCssIndicatorChangedRegistrants.notifyRegistrants();
         }
 
-        if (hasBandwidthChanged) {
-            mBandwidthChangedRegistrants.notifyRegistrants();
-        }
-
         if (hasRejectCauseChanged) {
             setNotification(CS_REJECT_CAUSE_ENABLED);
         }
@@ -4500,23 +4530,11 @@
         }
         Context context = mPhone.getContext();
 
-        if (mPhone.isSubscriptionManagerServiceEnabled()) {
-            SubscriptionInfoInternal subInfo = mSubscriptionManagerService
-                    .getSubscriptionInfoInternal(mPhone.getSubId());
-            if (subInfo == null || !subInfo.isVisible()) {
-                log("cannot setNotification on invisible subid mSubId=" + mSubId);
-                return;
-            }
-        } else {
-            SubscriptionInfo info = mSubscriptionController
-                    .getActiveSubscriptionInfo(mPhone.getSubId(), context.getOpPackageName(),
-                            context.getAttributionTag());
-
-            //if subscription is part of a group and non-primary, suppress all notifications
-            if (info == null || (info.isOpportunistic() && info.getGroupUuid() != null)) {
-                log("cannot setNotification on invisible subid mSubId=" + mSubId);
-                return;
-            }
+        SubscriptionInfoInternal subInfo = mSubscriptionManagerService
+                .getSubscriptionInfoInternal(mPhone.getSubId());
+        if (subInfo == null || !subInfo.isVisible()) {
+            log("cannot setNotification on invisible subid mSubId=" + mSubId);
+            return;
         }
 
         // Needed because sprout RIL sends these when they shouldn't?
@@ -5304,9 +5322,8 @@
         pw.println(" mImsRegistrationOnOff=" + mImsRegistrationOnOff);
         pw.println(" pending radio off event="
                 + hasMessages(EVENT_POWER_OFF_RADIO_IMS_DEREG_TIMEOUT));
-        pw.println(" mRadioDisabledByCarrier" + mRadioDisabledByCarrier);
+        pw.println(" mRadioPowerOffReasons=" + mRadioPowerOffReasons);
         pw.println(" mDeviceShuttingDown=" + mDeviceShuttingDown);
-        pw.println(" mSpnUpdatePending=" + mSpnUpdatePending);
         pw.println(" mCellInfoMinIntervalMs=" + mCellInfoMinIntervalMs);
         pw.println(" mEriManager=" + mEriManager);
 
@@ -5529,8 +5546,7 @@
     }
 
     /**
-     * This method adds IWLAN registration info for legacy mode devices camped on IWLAN. It also
-     * makes some adjustments when the device camps on IWLAN in airplane mode.
+     * This method makes some adjustments when the device camps on IWLAN in airplane mode.
      */
     private void processIwlanRegistrationInfo() {
         if (mCi.getRadioState() == TelephonyManager.RADIO_POWER_OFF) {
@@ -5544,7 +5560,7 @@
             }
             // operator info should be kept in SS
             String operator = mNewSS.getOperatorAlphaLong();
-            mNewSS.setOutOfService(mAccessNetworksManager.isInLegacyMode(), true);
+            mNewSS.setOutOfService(true);
             if (resetIwlanRatVal) {
                 mNewSS.setDataRegState(ServiceState.STATE_IN_SERVICE);
                 NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
@@ -5554,17 +5570,6 @@
                         .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
                         .build();
                 mNewSS.addNetworkRegistrationInfo(nri);
-                if (mAccessNetworksManager.isInLegacyMode()) {
-                    // If in legacy mode, simulate the behavior that IWLAN registration info
-                    // is reported through WWAN transport.
-                    nri = new NetworkRegistrationInfo.Builder()
-                            .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                            .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
-                            .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_IWLAN)
-                            .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
-                            .build();
-                    mNewSS.addNetworkRegistrationInfo(nri);
-                }
                 mNewSS.setOperatorAlphaLong(operator);
                 // Since it's in airplane mode, cellular must be out of service. The only possible
                 // transport for data to go through is the IWLAN transport. Setting this to true
@@ -5574,31 +5579,6 @@
             }
             return;
         }
-
-        // If the device operates in legacy mode and camps on IWLAN, modem reports IWLAN as a RAT
-        // through WWAN registration info. To be consistent with the behavior with AP-assisted mode,
-        // we manually make a WLAN registration info for clients to consume. In this scenario,
-        // both WWAN and WLAN registration info are the IWLAN registration info and that's the
-        // unfortunate limitation we have when the device operates in legacy mode. In AP-assisted
-        // mode, the WWAN registration will correctly report the actual cellular registration info
-        // when the device camps on IWLAN.
-        if (mAccessNetworksManager.isInLegacyMode()) {
-            NetworkRegistrationInfo wwanNri = mNewSS.getNetworkRegistrationInfo(
-                    NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-            if (wwanNri != null && wwanNri.getAccessNetworkTechnology()
-                    == TelephonyManager.NETWORK_TYPE_IWLAN) {
-                NetworkRegistrationInfo wlanNri = new NetworkRegistrationInfo.Builder()
-                        .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
-                        .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
-                        .setRegistrationState(wwanNri.getNetworkRegistrationState())
-                        .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_IWLAN)
-                        .setRejectCause(wwanNri.getRejectCause())
-                        .setEmergencyOnly(wwanNri.isEmergencyEnabled())
-                        .setAvailableServices(wwanNri.getAvailableServices())
-                        .build();
-                mNewSS.addNetworkRegistrationInfo(wlanNri);
-            }
-        }
     }
 
     /**
@@ -5856,25 +5836,6 @@
     }
 
     /**
-     * Registers for cell bandwidth changed.
-     * @param h handler to notify
-     * @param what what code of message when delivered
-     * @param obj placed in Message.obj
-     */
-    public void registerForBandwidthChanged(Handler h, int what, Object obj) {
-        Registrant r = new Registrant(h, what, obj);
-        mBandwidthChangedRegistrants.add(r);
-    }
-
-    /**
-     * Unregisters for cell bandwidth changed.
-     * @param h handler to notify
-     */
-    public void unregisterForBandwidthChanged(Handler h) {
-        mBandwidthChangedRegistrants.remove(h);
-    }
-
-    /**
      * Get the NR data connection context ids.
      *
      * @return data connection context ids.
diff --git a/src/java/com/android/internal/telephony/SignalStrengthController.java b/src/java/com/android/internal/telephony/SignalStrengthController.java
index 150df57..8c35e57 100644
--- a/src/java/com/android/internal/telephony/SignalStrengthController.java
+++ b/src/java/com/android/internal/telephony/SignalStrengthController.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony;
 
+import static android.telephony.TelephonyManager.HAL_SERVICE_NETWORK;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -347,16 +349,9 @@
                 || (curTime - mSignalStrengthUpdatedTime > SIGNAL_STRENGTH_REFRESH_THRESHOLD_IN_MS);
         if (!isStale) return false;
 
-        List<SubscriptionInfo> subInfoList;
-        if (mPhone.isSubscriptionManagerServiceEnabled()) {
-            subInfoList = SubscriptionManagerService.getInstance().getActiveSubscriptionInfoList(
-                    mPhone.getContext().getOpPackageName(),
-                    mPhone.getContext().getAttributionTag());
-        } else {
-            subInfoList = SubscriptionController.getInstance()
-                    .getActiveSubscriptionInfoList(mPhone.getContext().getOpPackageName(),
-                            mPhone.getContext().getAttributionTag());
-        }
+        List<SubscriptionInfo> subInfoList = SubscriptionManagerService.getInstance()
+                .getActiveSubscriptionInfoList(mPhone.getContext().getOpPackageName(),
+                        mPhone.getContext().getAttributionTag());
 
         if (!ArrayUtils.isEmpty(subInfoList)) {
             for (SubscriptionInfo info : subInfoList) {
@@ -420,7 +415,7 @@
                             (lteMeasurementEnabled & CellSignalStrengthLte.USE_RSRP) != 0));
         }
 
-        if (mPhone.getHalVersion().greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) {
+        if (mPhone.getHalVersion(HAL_SERVICE_NETWORK).greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) {
             int[] lteRsrqThresholds = mCarrierConfig.getIntArray(
                     CarrierConfigManager.KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY);
             if (lteRsrqThresholds != null) {
@@ -477,6 +472,18 @@
                                 AccessNetworkConstants.AccessNetworkType.NGRAN,
                                 (nrMeasurementEnabled & CellSignalStrengthNr.USE_SSSINR) != 0));
             }
+
+            int[] wcdmaEcnoThresholds = mCarrierConfig.getIntArray(
+                    CarrierConfigManager.KEY_WCDMA_ECNO_THRESHOLDS_INT_ARRAY);
+            if (wcdmaEcnoThresholds != null) {
+                signalThresholdInfos.add(
+                        createSignalThresholdsInfo(
+                                SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_ECNO,
+                                wcdmaEcnoThresholds,
+                                AccessNetworkConstants.AccessNetworkType.UTRAN,
+                                false));
+            }
+
         }
 
         consolidatedAndSetReportingCriteria(signalThresholdInfos);
@@ -510,7 +517,7 @@
                         AccessNetworkConstants.AccessNetworkType.CDMA2000,
                         true));
 
-        if (mPhone.getHalVersion().greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) {
+        if (mPhone.getHalVersion(HAL_SERVICE_NETWORK).greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) {
             signalThresholdInfos.add(
                     createSignalThresholdsInfo(
                             SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ,
@@ -543,6 +550,12 @@
                             AccessNetworkThresholds.NGRAN_SSSINR,
                             AccessNetworkConstants.AccessNetworkType.NGRAN,
                             false));
+            signalThresholdInfos.add(
+                    createSignalThresholdsInfo(
+                            SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_ECNO,
+                            AccessNetworkThresholds.UTRAN_ECNO,
+                            AccessNetworkConstants.AccessNetworkType.UTRAN,
+                            false));
         }
 
         consolidatedAndSetReportingCriteria(signalThresholdInfos);
@@ -571,12 +584,14 @@
                             measurementType,
                             mPhone.getSubId(),
                             mPhone.isDeviceIdle());
+            int hysteresisDb = getMinimumHysteresisDb(isEnabledForAppRequest, ran, measurementType,
+                    consolidatedThresholds);
             consolidatedSignalThresholdInfos.add(
                     new SignalThresholdInfo.Builder()
                             .setRadioAccessNetworkType(ran)
                             .setSignalMeasurementType(measurementType)
                             .setHysteresisMs(REPORTING_HYSTERESIS_MILLIS)
-                            .setHysteresisDb(REPORTING_HYSTERESIS_DB)
+                            .setHysteresisDb(hysteresisDb)
                             .setThresholds(consolidatedThresholds, true /*isSystem*/)
                             .setIsEnabled(isEnabledForSystem || isEnabledForAppRequest)
                             .build());
@@ -587,6 +602,126 @@
                         + consolidatedSignalThresholdInfos);
     }
 
+    /**
+     * Return the minimum hysteresis dB from all available sources:
+     * - system default
+     * - value set by client through API
+     * - threshold delta
+     */
+    @VisibleForTesting
+    public int getMinimumHysteresisDb(boolean isEnabledForAppRequest, int ran, int measurementType,
+              final int[] consolidatedThresholdList) {
+
+        int currHysteresisDb = getHysteresisDbFromCarrierConfig(ran, measurementType);
+
+        if (isEnabledForAppRequest) {
+            // Get minimum hysteresisDb at api
+            int apiHysteresisDb =
+                    getHysteresisDbFromSignalThresholdInfoRequests(ran, measurementType);
+
+            // Choose minimum of hysteresisDb between api Vs current system/cc value set
+            currHysteresisDb = Math.min(currHysteresisDb, apiHysteresisDb);
+
+            // Hal Req: choose hysteresis db value to be smaller of smallest of threshold delta
+            currHysteresisDb =  computeHysteresisDbOnSmallestThresholdDelta(
+                    currHysteresisDb, consolidatedThresholdList);
+        }
+        return currHysteresisDb;
+    }
+
+    /**
+     * Get the hysteresis db value from Signal Requests
+     * Note: Based on the current use case, there does not exist multile App signal threshold info
+     * requests with hysteresis db value, so this logic picks the latest hysteresis db value set.
+     *
+     * TODO(b/262655157): Support Multiple App Hysteresis DB value customisation
+     */
+    private int getHysteresisDbFromSignalThresholdInfoRequests(
+            @AccessNetworkConstants.RadioAccessNetworkType int ran,
+            @SignalThresholdInfo.SignalMeasurementType int measurement) {
+        int apiHysteresisDb = REPORTING_HYSTERESIS_DB;
+        for (SignalRequestRecord record : mSignalRequestRecords) {
+            for (SignalThresholdInfo info : record.mRequest.getSignalThresholdInfos()) {
+                if (isRanAndSignalMeasurementTypeMatch(ran, measurement, info)) {
+                    if (info.getHysteresisDb() >= 0) {
+                        apiHysteresisDb = info.getHysteresisDb();
+                    }
+                }
+            }
+        }
+        return apiHysteresisDb;
+    }
+
+    private int getHysteresisDbFromCarrierConfig(int ran, int measurement) {
+        int configHysteresisDb = REPORTING_HYSTERESIS_DB;
+        String configKey = null;
+
+        switch (ran) {
+            case AccessNetworkConstants.AccessNetworkType.GERAN:
+                if (measurement == SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI) {
+                    configKey = CarrierConfigManager.KEY_GERAN_RSSI_HYSTERESIS_DB_INT;
+                }
+                break;
+            case AccessNetworkConstants.AccessNetworkType.UTRAN:
+                if (measurement == SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP) {
+                    configKey = CarrierConfigManager.KEY_UTRAN_RSCP_HYSTERESIS_DB_INT;
+                } else if (measurement == SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_ECNO) {
+                    configKey = CarrierConfigManager.KEY_UTRAN_ECNO_HYSTERESIS_DB_INT;
+                }
+                break;
+            case AccessNetworkConstants.AccessNetworkType.EUTRAN:
+                if (measurement == SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP) {
+                    configKey = CarrierConfigManager.KEY_EUTRAN_RSRP_HYSTERESIS_DB_INT;
+                } else if (measurement == SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ) {
+                    configKey = CarrierConfigManager.KEY_EUTRAN_RSRQ_HYSTERESIS_DB_INT;
+                } else if (measurement == SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR) {
+                    configKey = CarrierConfigManager.KEY_EUTRAN_RSSNR_HYSTERESIS_DB_INT;
+                }
+                break;
+            case AccessNetworkConstants.AccessNetworkType.NGRAN:
+                if (measurement == SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP) {
+                    configKey = CarrierConfigManager.KEY_NGRAN_SSRSRP_HYSTERESIS_DB_INT;
+                } else if (measurement == SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ) {
+                    configKey = CarrierConfigManager.KEY_NGRAN_SSRSRQ_HYSTERESIS_DB_INT;
+                } else if (measurement == SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR) {
+                    configKey = CarrierConfigManager.KEY_NGRAN_SSSINR_HYSTERESIS_DB_INT;
+                }
+                break;
+            default:
+                localLog("No matching configuration");
+        }
+        if (configKey != null) {
+            configHysteresisDb = mCarrierConfig.getInt(configKey, REPORTING_HYSTERESIS_DB);
+        }
+        return configHysteresisDb >= SignalThresholdInfo.HYSTERESIS_DB_MINIMUM
+                ? configHysteresisDb : REPORTING_HYSTERESIS_DB;
+    }
+
+    /**
+     * This method computes the hysteresis db value between smaller of the smallest Threshold Delta
+     * and system / cc / api hysteresis db value determined.
+     *
+     * @param currMinHysteresisDb  smaller value between system / cc / api hysteresis db value
+     * @param signalThresholdInfoArray consolidated threshold info with App request consolidated.
+     * @return current minimum hysteresis db value computed between above params.
+     *
+     */
+    private int computeHysteresisDbOnSmallestThresholdDelta(
+            int currMinHysteresisDb, final int[] signalThresholdInfoArray) {
+        int index = 0;
+        if (signalThresholdInfoArray.length > 1) {
+            while (index != signalThresholdInfoArray.length - 1) {
+                if (signalThresholdInfoArray[index + 1] - signalThresholdInfoArray[index]
+                        < currMinHysteresisDb) {
+                    currMinHysteresisDb =
+                            signalThresholdInfoArray[index + 1] - signalThresholdInfoArray[index];
+                }
+                index++;
+            }
+        }
+        return currMinHysteresisDb;
+    }
+
     void setSignalStrengthDefaultValues() {
         mSignalStrength = new SignalStrength();
         mSignalStrengthUpdatedTime = System.currentTimeMillis();
@@ -1125,6 +1260,16 @@
                 15, /* SIGNAL_STRENGTH_GOOD */
                 30  /* SIGNAL_STRENGTH_GREAT */
         };
+
+        /**
+         * List of dBm thresholds for UTRAN {@link AccessNetworkConstants.AccessNetworkType} ECNO
+         */
+        public static final int[] UTRAN_ECNO = new int[]{
+                -24, /* SIGNAL_STRENGTH_POOR */
+                -14, /* SIGNAL_STRENGTH_MODERATE */
+                -6, /* SIGNAL_STRENGTH_GOOD */
+                1  /* SIGNAL_STRENGTH_GREAT */
+        };
     }
 
     private static void log(String msg) {
diff --git a/src/java/com/android/internal/telephony/SimIndication.java b/src/java/com/android/internal/telephony/SimIndication.java
index 20f89da..d74a9b1 100644
--- a/src/java/com/android/internal/telephony/SimIndication.java
+++ b/src/java/com/android/internal/telephony/SimIndication.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony;
 
+import static android.telephony.TelephonyManager.HAL_SERVICE_SIM;
+
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED;
@@ -53,7 +55,7 @@
      * @param indicationType Type of radio indication
      */
     public void carrierInfoForImsiEncryption(int indicationType) {
-        mRil.processIndication(RIL.SIM_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_SIM, indicationType);
 
         if (mRil.isLogOrTrace()) {
             mRil.unsljLogRet(RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION, null);
@@ -69,7 +71,7 @@
      * @param cdmaSource New CdmaSubscriptionSource
      */
     public void cdmaSubscriptionSourceChanged(int indicationType, int cdmaSource) {
-        mRil.processIndication(RIL.SIM_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_SIM, indicationType);
 
         int[] response = new int[]{cdmaSource};
         if (mRil.isLogOrTrace()) {
@@ -85,7 +87,7 @@
      * @param indicationType Type of radio indication
      */
     public void simPhonebookChanged(int indicationType) {
-        mRil.processIndication(RIL.SIM_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_SIM, indicationType);
 
         if (mRil.isLogOrTrace()) {
             mRil.unsljLog(RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED);
@@ -102,7 +104,7 @@
      */
     public void simPhonebookRecordsReceived(int indicationType, byte status,
             android.hardware.radio.sim.PhonebookRecordInfo[] records) {
-        mRil.processIndication(RIL.SIM_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_SIM, indicationType);
 
         List<SimPhonebookRecord> simPhonebookRecords = new ArrayList<>();
 
@@ -127,7 +129,7 @@
      */
     public void simRefresh(int indicationType,
             android.hardware.radio.sim.SimRefreshResult refreshResult) {
-        mRil.processIndication(RIL.SIM_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_SIM, indicationType);
 
         IccRefreshResponse response = new IccRefreshResponse();
         response.refreshResult = refreshResult.type;
@@ -144,7 +146,7 @@
      * @param indicationType Type of radio indication
      */
     public void simStatusChanged(int indicationType) {
-        mRil.processIndication(RIL.SIM_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_SIM, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED);
 
@@ -159,7 +161,7 @@
      *        Refer to TS 102.223 section 9.4 for command types
      */
     public void stkEventNotify(int indicationType, String cmd) {
-        mRil.processIndication(RIL.SIM_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_SIM, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_STK_EVENT_NOTIFY);
 
@@ -175,7 +177,7 @@
      *        Refer to TS 102.223 section 9.4 for command types
      */
     public void stkProactiveCommand(int indicationType, String cmd) {
-        mRil.processIndication(RIL.SIM_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_SIM, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_STK_PROACTIVE_COMMAND);
 
@@ -189,7 +191,7 @@
      * @param indicationType Type of radio indication
      */
     public void stkSessionEnd(int indicationType) {
-        mRil.processIndication(RIL.SIM_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_SIM, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_STK_SESSION_END);
 
@@ -204,7 +206,7 @@
      * @param activate false for subscription deactivated, true for subscription activated
      */
     public void subscriptionStatusChanged(int indicationType, boolean activate) {
-        mRil.processIndication(RIL.SIM_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_SIM, indicationType);
 
         int[] response = new int[]{activate ? 1 : 0};
 
@@ -222,7 +224,7 @@
      * @param enabled Whether uiccApplications are enabled or disabled
      */
     public void uiccApplicationsEnablementChanged(int indicationType, boolean enabled) {
-        mRil.processIndication(RIL.SIM_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_SIM, indicationType);
 
         if (mRil.isLogOrTrace()) {
             mRil.unsljLogRet(RIL_UNSOL_UICC_APPLICATIONS_ENABLEMENT_CHANGED, enabled);
diff --git a/src/java/com/android/internal/telephony/SimResponse.java b/src/java/com/android/internal/telephony/SimResponse.java
index b0099fb..1e1dbe5 100644
--- a/src/java/com/android/internal/telephony/SimResponse.java
+++ b/src/java/com/android/internal/telephony/SimResponse.java
@@ -16,8 +16,11 @@
 
 package com.android.internal.telephony;
 
+import static android.telephony.TelephonyManager.HAL_SERVICE_SIM;
+
 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;
@@ -41,7 +44,7 @@
 
     private void responseIccIo(RadioResponseInfo responseInfo,
             android.hardware.radio.sim.IccIoResult result) {
-        RILRequest rr = mRil.processResponse(RIL.SIM_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_SIM, responseInfo);
 
         if (rr != null) {
             IccIoResult ret = new IccIoResult(result.sw1, result.sw2, result.simResponse);
@@ -68,7 +71,7 @@
      */
     public void areUiccApplicationsEnabledResponse(RadioResponseInfo responseInfo,
             boolean enabled) {
-        RILRequest rr = mRil.processResponse(RIL.SIM_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_SIM, responseInfo);
 
         if (rr != null) {
             if (responseInfo.error == RadioError.NONE) {
@@ -83,7 +86,7 @@
      * @param remainingAttempts Number of retries remaining, must be equal to -1 if unknown.
      */
     public void changeIccPin2ForAppResponse(RadioResponseInfo responseInfo, int remainingAttempts) {
-        RadioResponse.responseInts(RIL.SIM_SERVICE, mRil, responseInfo, remainingAttempts);
+        RadioResponse.responseInts(HAL_SERVICE_SIM, mRil, responseInfo, remainingAttempts);
     }
 
     /**
@@ -91,14 +94,14 @@
      * @param remainingAttempts Number of retries remaining, must be equal to -1 if unknown.
      */
     public void changeIccPinForAppResponse(RadioResponseInfo responseInfo, int remainingAttempts) {
-        RadioResponse.responseInts(RIL.SIM_SERVICE, mRil, responseInfo, remainingAttempts);
+        RadioResponse.responseInts(HAL_SERVICE_SIM, mRil, responseInfo, remainingAttempts);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error.
      */
     public void enableUiccApplicationsResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.SIM_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_SIM, mRil, responseInfo);
     }
 
     /**
@@ -109,7 +112,7 @@
     public void getAllowedCarriersResponse(RadioResponseInfo responseInfo,
             android.hardware.radio.sim.CarrierRestrictions carrierRestrictions,
             int multiSimPolicy) {
-        RILRequest rr = mRil.processResponse(RIL.SIM_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_SIM, responseInfo);
         if (rr == null) {
             return;
         }
@@ -125,7 +128,6 @@
         if (!carrierRestrictions.allowedCarriersPrioritized) {
             carrierRestrictionDefault = CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_ALLOWED;
         }
-
         ret = CarrierRestrictionRules.newBuilder()
                 .setAllowedCarriers(RILUtils.convertHalCarrierList(
                         carrierRestrictions.allowedCarriers))
@@ -133,6 +135,7 @@
                         carrierRestrictions.excludedCarriers))
                 .setDefaultCarrierRestriction(carrierRestrictionDefault)
                 .setMultiSimPolicy(policy)
+                .setCarrierRestrictionStatus(carrierRestrictions.status)
                 .build();
 
         if (responseInfo.error == RadioError.NONE) {
@@ -154,7 +157,7 @@
     public void getCdmaSubscriptionResponse(RadioResponseInfo responseInfo, String mdn,
             String hSid, String hNid, String min, String prl) {
         RadioResponse.responseStrings(
-                RIL.SIM_SERVICE, mRil, responseInfo, mdn, hSid, hNid, min, prl);
+                HAL_SERVICE_SIM, mRil, responseInfo, mdn, hSid, hNid, min, prl);
     }
 
     /**
@@ -162,7 +165,7 @@
      * @param source CDMA subscription source
      */
     public void getCdmaSubscriptionSourceResponse(RadioResponseInfo responseInfo, int source) {
-        RadioResponse.responseInts(RIL.SIM_SERVICE, mRil, responseInfo, source);
+        RadioResponse.responseInts(HAL_SERVICE_SIM, mRil, responseInfo, source);
     }
 
     /**
@@ -171,7 +174,7 @@
      *        specified barring facility is active. "0" means "disabled for all"
      */
     public void getFacilityLockForAppResponse(RadioResponseInfo responseInfo, int response) {
-        RadioResponse.responseInts(RIL.SIM_SERVICE, mRil, responseInfo, response);
+        RadioResponse.responseInts(HAL_SERVICE_SIM, mRil, responseInfo, response);
     }
 
     /**
@@ -180,7 +183,7 @@
      */
     public void getIccCardStatusResponse(RadioResponseInfo responseInfo,
             android.hardware.radio.sim.CardStatus cardStatus) {
-        RILRequest rr = mRil.processResponse(RIL.SIM_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_SIM, responseInfo);
 
         if (rr != null) {
             IccCardStatus iccCardStatus = RILUtils.convertHalCardStatus(cardStatus);
@@ -197,7 +200,7 @@
      * @param imsi String containing the IMSI
      */
     public void getImsiForAppResponse(RadioResponseInfo responseInfo, String imsi) {
-        RadioResponse.responseString(RIL.SIM_SERVICE, mRil, responseInfo, imsi);
+        RadioResponse.responseString(HAL_SERVICE_SIM, mRil, responseInfo, imsi);
     }
 
     /**
@@ -207,7 +210,7 @@
     public void getSimPhonebookCapacityResponse(RadioResponseInfo responseInfo,
             android.hardware.radio.sim.PhonebookCapacity pbCapacity) {
         AdnCapacity capacity = RILUtils.convertHalPhonebookCapacity(pbCapacity);
-        RILRequest rr = mRil.processResponse(RIL.SIM_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_SIM, responseInfo);
         if (rr != null) {
             if (responseInfo.error == RadioError.NONE) {
                 RadioResponse.sendMessageResponse(rr.mResult, capacity);
@@ -220,14 +223,21 @@
      * @param responseInfo Response info struct containing response type, serial no. and error.
      */
     public void getSimPhonebookRecordsResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.SIM_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_SIM, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void iccCloseLogicalChannelResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.SIM_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_SIM, mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void iccCloseLogicalChannelWithSessionInfoResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(HAL_SERVICE_SIM, mRil, responseInfo);
     }
 
     /**
@@ -252,7 +262,7 @@
         for (int i = 0; i < selectResponse.length; i++) {
             arr.add((int) selectResponse[i]);
         }
-        RadioResponse.responseIntArrayList(RIL.SIM_SERVICE, mRil, responseInfo, arr);
+        RadioResponse.responseIntArrayList(HAL_SERVICE_SIM, mRil, responseInfo, arr);
     }
 
     /**
@@ -277,7 +287,7 @@
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void reportStkServiceIsRunningResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.SIM_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_SIM, mRil, responseInfo);
     }
 
     /**
@@ -286,7 +296,7 @@
      */
     public void requestIccSimAuthenticationResponse(RadioResponseInfo responseInfo,
             android.hardware.radio.sim.IccIoResult iccIo) {
-        RILRequest rr = mRil.processResponse(RIL.SIM_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_SIM, responseInfo);
 
         if (rr != null) {
             IccIoResult ret = new IccIoResult(iccIo.sw1, iccIo.sw2,
@@ -315,7 +325,7 @@
      *        string starting with first byte of response
      */
     public void sendEnvelopeResponse(RadioResponseInfo responseInfo, String commandResponse) {
-        RadioResponse.responseString(RIL.SIM_SERVICE, mRil, responseInfo, commandResponse);
+        RadioResponse.responseString(HAL_SERVICE_SIM, mRil, responseInfo, commandResponse);
     }
 
     /**
@@ -331,7 +341,7 @@
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void sendTerminalResponseToSimResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.SIM_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_SIM, mRil, responseInfo);
     }
 
     /**
@@ -339,7 +349,7 @@
      */
     public void setAllowedCarriersResponse(RadioResponseInfo responseInfo) {
         int ret = TelephonyManager.SET_CARRIER_RESTRICTION_ERROR;
-        RILRequest rr = mRil.processResponse(RIL.SIM_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_SIM, responseInfo);
         if (rr != null) {
             mRil.riljLog("setAllowedCarriersResponse - error = " + responseInfo.error);
 
@@ -355,14 +365,14 @@
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setCarrierInfoForImsiEncryptionResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.SIM_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_SIM, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setCdmaSubscriptionSourceResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.SIM_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_SIM, mRil, responseInfo);
     }
 
     /**
@@ -370,21 +380,21 @@
      * @param retry 0 is the number of retries remaining, or -1 if unknown
      */
     public void setFacilityLockForAppResponse(RadioResponseInfo responseInfo, int retry) {
-        RadioResponse.responseInts(RIL.SIM_SERVICE, mRil, responseInfo, retry);
+        RadioResponse.responseInts(HAL_SERVICE_SIM, mRil, responseInfo, retry);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setSimCardPowerResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.SIM_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_SIM, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setUiccSubscriptionResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.SIM_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_SIM, mRil, responseInfo);
     }
 
     /**
@@ -392,7 +402,7 @@
      * @param remainingAttempts Number of retries remaining, must be equal to -1 if unknown.
      */
     public void supplyIccPin2ForAppResponse(RadioResponseInfo responseInfo, int remainingAttempts) {
-        RadioResponse.responseInts(RIL.SIM_SERVICE, mRil, responseInfo, remainingAttempts);
+        RadioResponse.responseInts(HAL_SERVICE_SIM, mRil, responseInfo, remainingAttempts);
     }
 
     /**
@@ -400,7 +410,7 @@
      * @param remainingAttempts Number of retries remaining, must be equal to -1 if unknown.
      */
     public void supplyIccPinForAppResponse(RadioResponseInfo responseInfo, int remainingAttempts) {
-        RadioResponse.responseInts(RIL.SIM_SERVICE, mRil, responseInfo, remainingAttempts);
+        RadioResponse.responseInts(HAL_SERVICE_SIM, mRil, responseInfo, remainingAttempts);
     }
 
     /**
@@ -408,7 +418,7 @@
      * @param remainingAttempts Number of retries remaining, must be equal to -1 if unknown.
      */
     public void supplyIccPuk2ForAppResponse(RadioResponseInfo responseInfo, int remainingAttempts) {
-        RadioResponse.responseInts(RIL.SIM_SERVICE, mRil, responseInfo, remainingAttempts);
+        RadioResponse.responseInts(HAL_SERVICE_SIM, mRil, responseInfo, remainingAttempts);
     }
 
     /**
@@ -416,7 +426,7 @@
      * @param remainingAttempts Number of retries remaining, must be equal to -1 if unknown.
      */
     public void supplyIccPukForAppResponse(RadioResponseInfo responseInfo, int remainingAttempts) {
-        RadioResponse.responseInts(RIL.SIM_SERVICE, mRil, responseInfo, remainingAttempts);
+        RadioResponse.responseInts(HAL_SERVICE_SIM, mRil, responseInfo, remainingAttempts);
     }
 
     /**
@@ -428,7 +438,7 @@
     public void supplySimDepersonalizationResponse(RadioResponseInfo responseInfo, int persoType,
             int remainingRetries) {
         RadioResponse.responseInts(
-                RIL.SIM_SERVICE, mRil, responseInfo, persoType, remainingRetries);
+                HAL_SERVICE_SIM, mRil, responseInfo, persoType, remainingRetries);
     }
 
     /**
@@ -437,7 +447,7 @@
      */
     public void updateSimPhonebookRecordsResponse(RadioResponseInfo responseInfo,
             int updatedRecordIndex) {
-        RadioResponse.responseInts(RIL.SIM_SERVICE, mRil, responseInfo, updatedRecordIndex);
+        RadioResponse.responseInts(HAL_SERVICE_SIM, mRil, responseInfo, updatedRecordIndex);
     }
 
     @Override
diff --git a/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java b/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
index 2f39958..ecd6276 100644
--- a/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
+++ b/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
@@ -260,15 +260,8 @@
      * Send tracker to appropriate (3GPP or 3GPP2) inbound SMS handler for broadcast.
      */
     private static void broadcastSms(InboundSmsTracker tracker) {
-        InboundSmsHandler handler;
         int subId = tracker.getSubId();
-        int phoneId;
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            phoneId = SubscriptionManagerService.getInstance().getPhoneId(subId);
-        } else {
-            // TODO consider other subs in this subId's group as well
-            phoneId = SubscriptionController.getInstance().getPhoneId(subId);
-        }
+        int phoneId = SubscriptionManagerService.getInstance().getPhoneId(subId);
         if (!SubscriptionManager.isValidPhoneId(phoneId)) {
             Rlog.e(TAG, "broadcastSms: ignoring message; no phone found for subId " + subId);
             return;
@@ -279,7 +272,7 @@
                     + " phoneId " + phoneId);
             return;
         }
-        handler = phone.getInboundSmsHandler(tracker.is3gpp2());
+        InboundSmsHandler handler = phone.getInboundSmsHandler(tracker.is3gpp2());
         if (handler != null) {
             handler.sendMessage(InboundSmsHandler.EVENT_BROADCAST_SMS, tracker);
         } else {
diff --git a/src/java/com/android/internal/telephony/SmsController.java b/src/java/com/android/internal/telephony/SmsController.java
index 08f7dd0..97161f8 100644
--- a/src/java/com/android/internal/telephony/SmsController.java
+++ b/src/java/com/android/internal/telephony/SmsController.java
@@ -18,6 +18,8 @@
 
 package com.android.internal.telephony;
 
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
 import static com.android.internal.telephony.util.TelephonyUtils.checkDumpPermission;
 
 import android.annotation.Nullable;
@@ -26,13 +28,13 @@
 import android.app.PendingIntent;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.os.BaseBundle;
 import android.os.Binder;
 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;
@@ -43,6 +45,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
+import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.telephony.Rlog;
 
@@ -156,11 +159,13 @@
         if (callingPackage == null) {
             callingPackage = getCallingPackage();
         }
+        Rlog.d(LOG_TAG, "sendDataForSubscriber caller=" + callingPackage);
 
         // Check if user is associated with the subscription
         if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(mContext, subId,
-                Binder.getCallingUserHandle())) {
-            // TODO(b/258629881): Display error dialog.
+                Binder.getCallingUserHandle(), destAddr)) {
+            TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(mContext, subId,
+                    Binder.getCallingUid(), callingPackage);
             sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_USER_NOT_ALLOWED);
             return;
         }
@@ -206,9 +211,48 @@
             String callingAttributionTag, String destAddr, String scAddr, String text,
             PendingIntent sentIntent, PendingIntent deliveryIntent,
             boolean persistMessageForNonDefaultSmsApp, long messageId) {
+        sendTextForSubscriber(subId, callingPackage, callingAttributionTag, destAddr, scAddr,
+                text, sentIntent, deliveryIntent, persistMessageForNonDefaultSmsApp, messageId,
+                false, false);
+
+    }
+
+    /**
+     * @param subId Subscription Id
+     * @param callingAttributionTag the attribution tag of the caller
+     * @param destAddr the address to send the message to
+     * @param scAddr is the service center address or null to use
+     *  the current default SMSC
+     * @param text the body of the message to send
+     * @param sentIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is successfully sent, or failed.
+     *  The result code will be <code>Activity.RESULT_OK</code> for success, or relevant errors
+     *  the sentIntent may include the extra "errorCode" containing a radio technology specific
+     *  value, generally only useful for troubleshooting.
+     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is delivered to the recipient.  The
+     *  raw pdu of the status report is in the extended data ("pdu").
+     * @param skipFdnCheck if set to true, FDN check must be skipped .This is set in case of STK sms
+     *
+     * @hide
+     */
+    public void sendTextForSubscriber(int subId, String callingPackage,
+            String callingAttributionTag, String destAddr, String scAddr, String text,
+            PendingIntent sentIntent, PendingIntent deliveryIntent,
+            boolean persistMessageForNonDefaultSmsApp, long messageId, boolean skipFdnCheck,
+            boolean skipShortCodeCheck) {
         if (callingPackage == null) {
             callingPackage = getCallingPackage();
         }
+        Rlog.d(LOG_TAG, "sendTextForSubscriber caller=" + callingPackage);
+
+        if (skipFdnCheck || skipShortCodeCheck) {
+            if (mContext.checkCallingOrSelfPermission(
+                    android.Manifest.permission.MODIFY_PHONE_STATE)
+                    != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("Requires MODIFY_PHONE_STATE permission.");
+            }
+        }
         if (!getSmsPermissions(subId).checkCallingCanSendText(persistMessageForNonDefaultSmsApp,
                 callingPackage, callingAttributionTag, "Sending SMS message")) {
             sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_GENERIC_FAILURE);
@@ -216,15 +260,21 @@
         }
 
         // Check if user is associated with the subscription
-        if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(mContext, subId,
-                Binder.getCallingUserHandle())) {
-            // TODO(b/258629881): Display error dialog.
+        boolean crossUserFullGranted = mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) == PERMISSION_GRANTED;
+        Rlog.d(LOG_TAG, "sendTextForSubscriber: caller has INTERACT_ACROSS_USERS_FULL? "
+                + crossUserFullGranted);
+        if (!crossUserFullGranted
+                && !TelephonyPermissions.checkSubscriptionAssociatedWithUser(mContext, subId,
+                Binder.getCallingUserHandle(), destAddr)) {
+            TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(mContext, subId,
+                    Binder.getCallingUid(), callingPackage);
             sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_USER_NOT_ALLOWED);
             return;
         }
 
         // Perform FDN check
-        if (isNumberBlockedByFDN(subId, destAddr, callingPackage)) {
+        if (!skipFdnCheck && isNumberBlockedByFDN(subId, destAddr, callingPackage)) {
             sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE);
             return;
         }
@@ -241,7 +291,7 @@
             sendBluetoothText(info, destAddr, text, sentIntent, deliveryIntent);
         } else {
             sendIccText(subId, callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
-                    persistMessageForNonDefaultSmsApp, messageId);
+                    persistMessageForNonDefaultSmsApp, messageId, skipShortCodeCheck);
         }
     }
 
@@ -258,16 +308,17 @@
 
     private void sendIccText(int subId, String callingPackage, String destAddr,
             String scAddr, String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
-            boolean persistMessageForNonDefaultSmsApp, long messageId) {
+            boolean persistMessageForNonDefaultSmsApp, long messageId, boolean skipShortCodeCheck) {
         Rlog.d(LOG_TAG, "sendTextForSubscriber iccSmsIntMgr"
-                + " Subscription: " + subId + " id: " + messageId);
+                + " Subscription: " + subId + " " + formatCrossStackMessageId(messageId));
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
             iccSmsIntMgr.sendText(callingPackage, destAddr, scAddr, text, sentIntent,
-                    deliveryIntent, persistMessageForNonDefaultSmsApp, messageId);
+                    deliveryIntent, persistMessageForNonDefaultSmsApp, messageId,
+                    skipShortCodeCheck);
         } else {
             Rlog.e(LOG_TAG, "sendTextForSubscriber iccSmsIntMgr is null for"
-                    + " Subscription: " + subId + " id: " + messageId);
+                    + " Subscription: " + subId + " " + formatCrossStackMessageId(messageId));
             sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_GENERIC_FAILURE);
         }
     }
@@ -295,11 +346,13 @@
         if (callingPackage == null) {
             callingPackage = getCallingPackage();
         }
+        Rlog.d(LOG_TAG, "sendTextForSubscriberWithOptions caller=" + callingPackage);
 
         // Check if user is associated with the subscription
         if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(mContext, subId,
-                Binder.getCallingUserHandle())) {
-            // TODO(b/258629881): Display error dialog.
+                Binder.getCallingUserHandle(), destAddr)) {
+            TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(mContext, subId,
+                    Binder.getCallingUid(), callingPackage);
             sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_USER_NOT_ALLOWED);
             return;
         }
@@ -332,11 +385,13 @@
         if (getCallingPackage() != null) {
             callingPackage = getCallingPackage();
         }
+        Rlog.d(LOG_TAG, "sendMultipartTextForSubscriber caller=" + callingPackage);
 
         // Check if user is associated with the subscription
         if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(mContext, subId,
-                Binder.getCallingUserHandle())) {
-            // TODO(b/258629881): Display error dialog.
+                Binder.getCallingUserHandle(), destAddr)) {
+            TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(mContext, subId,
+                    Binder.getCallingUid(), callingPackage);
             sendErrorInPendingIntents(sentIntents, SmsManager.RESULT_USER_NOT_ALLOWED);
             return;
         }
@@ -354,7 +409,7 @@
                     messageId);
         } else {
             Rlog.e(LOG_TAG, "sendMultipartTextForSubscriber iccSmsIntMgr is null for"
-                    + " Subscription: " + subId + " id: " + messageId);
+                    + " Subscription: " + subId + " " + formatCrossStackMessageId(messageId));
             sendErrorInPendingIntents(sentIntents, SmsManager.RESULT_ERROR_GENERIC_FAILURE);
         }
     }
@@ -367,11 +422,13 @@
         if (callingPackage == null) {
             callingPackage = getCallingPackage();
         }
+        Rlog.d(LOG_TAG, "sendMultipartTextForSubscriberWithOptions caller=" + callingPackage);
 
         // Check if user is associated with the subscription
         if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(mContext, subId,
-                Binder.getCallingUserHandle())) {
-            // TODO(b/258629881): Display error dialog.
+                Binder.getCallingUserHandle(), destAddr)) {
+            TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(mContext, subId,
+                    Binder.getCallingUid(), callingPackage);
             sendErrorInPendingIntents(sentIntents, SmsManager.RESULT_USER_NOT_ALLOWED);
             return;
         }
@@ -490,7 +547,7 @@
         // Don't show the SMS SIM Pick activity if it is not foreground.
         boolean isCallingProcessForeground = am != null
                 && am.getUidImportance(Binder.getCallingUid())
-                        == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+                == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
         if (!isCallingProcessForeground) {
             Rlog.d(LOG_TAG, "isSmsSimPickActivityNeeded: calling process not foreground. "
                     + "Suppressing activity.");
@@ -561,32 +618,19 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @Override
     public int getPreferredSmsSubscription() {
-        int defaultSubId;
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            // If there is a default, choose that one.
-            defaultSubId = SubscriptionManagerService.getInstance().getDefaultSmsSubId();
-        } else {
-            // If there is a default, choose that one.
-            defaultSubId = SubscriptionController.getInstance().getDefaultSmsSubId();
-        }
+        // If there is a default, choose that one.
+        int defaultSubId = SubscriptionManagerService.getInstance().getDefaultSmsSubId();
+
         if (SubscriptionManager.isValidSubscriptionId(defaultSubId)) {
             return defaultSubId;
         }
         // No default, if there is only one sub active, choose that as the "preferred" sub id.
         long token = Binder.clearCallingIdentity();
         try {
-            if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-                int[] activeSubs = SubscriptionManagerService.getInstance()
-                        .getActiveSubIdList(true /*visibleOnly*/);
-                if (activeSubs.length == 1) {
-                    return activeSubs[0];
-                }
-            } else {
-                int[] activeSubs = SubscriptionController.getInstance()
-                        .getActiveSubIdList(true /*visibleOnly*/);
-                if (activeSubs.length == 1) {
-                    return activeSubs[0];
-                }
+            int[] activeSubs = SubscriptionManagerService.getInstance()
+                    .getActiveSubIdList(true /*visibleOnly*/);
+            if (activeSubs.length == 1) {
+                return activeSubs[0];
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -614,6 +658,8 @@
             throw new SecurityException("sendStoredText: Package " + callingPkg
                     + "does not belong to " + Binder.getCallingUid());
         }
+        Rlog.d(LOG_TAG, "sendStoredText caller=" + callingPkg);
+
         if (iccSmsIntMgr != null) {
             iccSmsIntMgr.sendStoredText(callingPkg, callingAttributionTag, messageUri, scAddress,
                     sentIntent, deliveryIntent);
@@ -632,6 +678,8 @@
             throw new SecurityException("sendStoredMultipartText: Package " + callingPkg
                     + " does not belong to " + Binder.getCallingUid());
         }
+        Rlog.d(LOG_TAG, "sendStoredMultipartText caller=" + callingPkg);
+
         if (iccSmsIntMgr != null) {
             iccSmsIntMgr.sendStoredMultipartText(callingPkg, callingAttributionTag, messageUri,
                     scAddress, sentIntents, deliveryIntents);
@@ -785,6 +833,58 @@
     }
 
     @Override
+    public void setStorageMonitorMemoryStatusOverride(int subId, boolean isStorageAvailable) {
+        Phone phone = getPhone(subId);
+        Context context;
+        if (phone != null) {
+            context = phone.getContext();
+        } else {
+            Rlog.e(LOG_TAG, "Phone Object is Null");
+            return;
+        }
+        // If it doesn't have modify phone state permission
+        // a SecurityException will be thrown.
+        if (context.checkPermission(android.Manifest
+                        .permission.MODIFY_PHONE_STATE, Binder.getCallingPid(),
+                        Binder.getCallingUid()) != PERMISSION_GRANTED) {
+            throw new SecurityException(
+                    "setStorageMonitorMemoryStatusOverride needs MODIFY_PHONE_STATE");
+        }
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            phone.mSmsStorageMonitor.sendMemoryStatusOverride(isStorageAvailable);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public void clearStorageMonitorMemoryStatusOverride(int subId) {
+        Phone phone = getPhone(subId);
+        Context context;
+        if (phone != null) {
+            context = phone.getContext();
+        } else {
+            Rlog.e(LOG_TAG, "Phone Object is Null");
+            return;
+        }
+        // If it doesn't have modify phone state permission
+        // a SecurityException will be thrown.
+        if (context.checkPermission(android.Manifest
+                        .permission.MODIFY_PHONE_STATE, Binder.getCallingPid(),
+                        Binder.getCallingUid()) != PERMISSION_GRANTED) {
+            throw new SecurityException(
+                    "clearStorageMonitorMemoryStatusOverride needs MODIFY_PHONE_STATE");
+        }
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            phone.mSmsStorageMonitor.clearMemoryStatusOverride();
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
     public int checkSmsShortCodeDestination(int subId, String callingPackage,
             String callingFeatureId, String destAddress, String countryIso) {
         if (callingPackage == null) {
@@ -809,17 +909,20 @@
     public void sendVisualVoicemailSmsForSubscriber(String callingPackage,
             String callingAttributionTag, int subId, String number, int port, String text,
             PendingIntent sentIntent) {
+        Rlog.d(LOG_TAG, "sendVisualVoicemailSmsForSubscriber caller=" + callingPackage);
+
         // Do not send non-emergency SMS in ECBM as it forces device to exit ECBM.
         if(getPhone(subId).isInEcm()) {
             Rlog.d(LOG_TAG, "sendVisualVoicemailSmsForSubscriber: Do not send non-emergency "
-                + "SMS in ECBM as it forces device to exit ECBM.");
+                    + "SMS in ECBM as it forces device to exit ECBM.");
             return;
         }
 
         // Check if user is associated with the subscription
         if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(mContext, subId,
-                Binder.getCallingUserHandle())) {
-            // TODO(b/258629881): Display error dialog.
+                Binder.getCallingUserHandle(), number)) {
+            TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(mContext, subId,
+                    Binder.getCallingUid(), callingPackage);
             sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_USER_NOT_ALLOWED);
             return;
         }
@@ -1004,7 +1107,7 @@
             }
         } else {
             Rlog.e(LOG_TAG, "getSmscAddressFromIccEfForSubscriber iccSmsIntMgr is null"
-                + " for Subscription: " + subId);
+                    + " for Subscription: " + subId);
             return true;
         }
 
diff --git a/src/java/com/android/internal/telephony/SmsDispatchersController.java b/src/java/com/android/internal/telephony/SmsDispatchersController.java
index 04927ed..d2dfcac 100644
--- a/src/java/com/android/internal/telephony/SmsDispatchersController.java
+++ b/src/java/com/android/internal/telephony/SmsDispatchersController.java
@@ -20,6 +20,8 @@
 import static com.android.internal.telephony.cdma.sms.BearerData.ERROR_NONE;
 import static com.android.internal.telephony.cdma.sms.BearerData.ERROR_TEMPORARY;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.app.PendingIntent.CanceledException;
@@ -29,19 +31,30 @@
 import android.content.IntentFilter;
 import android.net.Uri;
 import android.os.AsyncResult;
+import android.os.Binder;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.Message;
 import android.os.UserManager;
 import android.provider.Telephony.Sms;
 import android.provider.Telephony.Sms.Intents;
+import android.telephony.Annotation.DisconnectCauses;
+import android.telephony.DomainSelectionService;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ServiceState;
 import android.telephony.SmsManager;
 import android.telephony.SmsMessage;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 
 import com.android.ims.ImsManager;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.cdma.CdmaInboundSmsHandler;
 import com.android.internal.telephony.cdma.CdmaSMSDispatcher;
+import com.android.internal.telephony.domainselection.DomainSelectionConnection;
+import com.android.internal.telephony.domainselection.DomainSelectionResolver;
+import com.android.internal.telephony.domainselection.EmergencySmsDomainSelectionConnection;
+import com.android.internal.telephony.domainselection.SmsDomainSelectionConnection;
 import com.android.internal.telephony.gsm.GsmInboundSmsHandler;
 import com.android.internal.telephony.gsm.GsmSMSDispatcher;
 import com.android.telephony.Rlog;
@@ -50,6 +63,8 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
 
 /**
  *
@@ -115,6 +130,195 @@
             new HashMap<>();
 
     /**
+     * Testing interface for injecting mock DomainSelectionConnection and a flag to indicate
+     * whether the domain selection is supported.
+     */
+    @VisibleForTesting
+    public interface DomainSelectionResolverProxy {
+        /**
+         * Returns a {@link DomainSelectionConnection} created using the specified
+         * context and callback.
+         *
+         * @param phone The {@link Phone} instance.
+         * @param selectorType The domain selector type to identify the domain selection connection.
+         *                     A {@link DomainSelectionService#SELECTOR_TYPE_SMS} is used for SMS.
+         * @param isEmergency A flag to indicate whether this connection is
+         *                    for an emergency SMS or not.
+         */
+        @Nullable DomainSelectionConnection getDomainSelectionConnection(Phone phone,
+                @DomainSelectionService.SelectorType int selectorType, boolean isEmergency);
+
+        /**
+         * Checks if the device supports the domain selection service to route the call / SMS /
+         * supplementary services to the appropriate domain.
+         *
+         * @return {@code true} if the domain selection is supported on the device,
+         *         {@code false} otherwise.
+         */
+        boolean isDomainSelectionSupported();
+    }
+
+    private DomainSelectionResolverProxy mDomainSelectionResolverProxy =
+            new DomainSelectionResolverProxy() {
+                @Override
+                @Nullable
+                public DomainSelectionConnection getDomainSelectionConnection(Phone phone,
+                        @DomainSelectionService.SelectorType int selectorType,
+                        boolean isEmergency) {
+                    try {
+                        return DomainSelectionResolver.getInstance().getDomainSelectionConnection(
+                                phone, selectorType, isEmergency);
+                    } catch (IllegalStateException e) {
+                        // In general, DomainSelectionResolver is always initialized by TeleService,
+                        // but if it's not initialized (like in unit tests),
+                        // it returns null to perform the legacy behavior in this case.
+                        return null;
+                    }
+                }
+
+                @Override
+                public boolean isDomainSelectionSupported() {
+                    return DomainSelectionResolver.getInstance().isDomainSelectionSupported();
+                }
+            };
+
+    /** Stores the sending SMS information for a pending request. */
+    private class PendingRequest {
+        public static final int TYPE_DATA = 1;
+        public static final int TYPE_TEXT = 2;
+        public static final int TYPE_MULTIPART_TEXT = 3;
+        public static final int TYPE_RETRY_SMS = 4;
+
+        public final int type;
+        public final SMSDispatcher.SmsTracker tracker;
+        public final String callingPackage;
+        public final String destAddr;
+        public final String scAddr;
+        public final ArrayList<PendingIntent> sentIntents;
+        public final ArrayList<PendingIntent> deliveryIntents;
+        public final boolean isForVvm;
+        // sendData specific
+        public final byte[] data;
+        public final int destPort;
+        // sendText / sendMultipartText specific
+        public final ArrayList<String> texts;
+        public final Uri messageUri;
+        public final boolean persistMessage;
+        public final int priority;
+        public final boolean expectMore;
+        public final int validityPeriod;
+        public final long messageId;
+        public final boolean skipShortCodeCheck;
+
+        PendingRequest(int type, SMSDispatcher.SmsTracker tracker, String callingPackage,
+                String destAddr, String scAddr, ArrayList<PendingIntent> sentIntents,
+                ArrayList<PendingIntent> deliveryIntents, boolean isForVvm, byte[] data,
+                int destPort, ArrayList<String> texts, Uri messageUri, boolean persistMessage,
+                int priority, boolean expectMore, int validityPeriod, long messageId,
+                boolean skipShortCodeCheck) {
+            this.type = type;
+            this.tracker = tracker;
+            this.callingPackage = callingPackage;
+            this.destAddr = destAddr;
+            this.scAddr = scAddr;
+            this.sentIntents = sentIntents;
+            this.deliveryIntents = deliveryIntents;
+            this.isForVvm = isForVvm;
+
+            this.data = data;
+            this.destPort = destPort;
+
+            this.texts = texts;
+            this.messageUri = messageUri;
+            this.persistMessage = persistMessage;
+            this.priority = priority;
+            this.expectMore = expectMore;
+            this.validityPeriod = validityPeriod;
+            this.messageId = messageId;
+            this.skipShortCodeCheck = skipShortCodeCheck;
+        }
+    }
+
+    /**
+     * Manages the {@link DomainSelectionConnection} instance and its related information.
+     */
+    @VisibleForTesting
+    protected class DomainSelectionConnectionHolder
+            implements DomainSelectionConnection.DomainSelectionConnectionCallback {
+        private final boolean mEmergency;
+        // Manages the pending request while selecting a proper domain.
+        private final List<PendingRequest> mPendingRequests = new ArrayList<>();
+        // Manages the domain selection connections: MO SMS or emergency SMS.
+        private DomainSelectionConnection mConnection;
+
+        DomainSelectionConnectionHolder(boolean emergency) {
+            mEmergency = emergency;
+        }
+
+        /**
+         * Returns a {@link DomainSelectionConnection} instance.
+         */
+        public DomainSelectionConnection getConnection() {
+            return mConnection;
+        }
+
+        /**
+         * Returns a list of {@link PendingRequest} that was added
+         * while the domain selection is performed.
+         */
+        public List<PendingRequest> getPendingRequests() {
+            return mPendingRequests;
+        }
+
+        /**
+         * Checks whether or not the domain selection is requested.
+         * If there is no pending request, the domain selection request is needed to
+         * select a proper domain for MO SMS.
+         */
+        public boolean isDomainSelectionRequested() {
+            return !mPendingRequests.isEmpty();
+        }
+
+        /**
+         * Checks whether or not this holder is for an emergency SMS.
+         */
+        public boolean isEmergency() {
+            return mEmergency;
+        }
+
+        /**
+         * Clears all pending requests.
+         */
+        public void clearAllRequests() {
+            mPendingRequests.clear();
+        }
+
+        /**
+         * Add a new pending request.
+         */
+        public void addRequest(@NonNull PendingRequest request) {
+            mPendingRequests.add(request);
+        }
+
+        /**
+         * Sets a {@link DomainSelectionConnection} instance.
+         */
+        public void setConnection(DomainSelectionConnection connection) {
+            mConnection = connection;
+        }
+
+
+        @Override
+        public void onSelectionTerminated(@DisconnectCauses int cause) {
+            notifyDomainSelectionTerminated(this);
+        }
+    }
+
+    /** Manages the domain selection connections: MO SMS or emergency SMS. */
+    private DomainSelectionConnectionHolder mDscHolder;
+    private DomainSelectionConnectionHolder mEmergencyDscHolder;
+
+    /**
      * Puts a delivery pending tracker to the map based on the format.
      *
      * @param tracker the tracker awaiting a delivery status report.
@@ -129,6 +333,14 @@
 
     public SmsDispatchersController(Phone phone, SmsStorageMonitor storageMonitor,
             SmsUsageMonitor usageMonitor) {
+        this(phone, storageMonitor, usageMonitor, phone.getLooper());
+    }
+
+    @VisibleForTesting
+    public SmsDispatchersController(Phone phone, SmsStorageMonitor storageMonitor,
+            SmsUsageMonitor usageMonitor, Looper looper) {
+        super(looper);
+
         Rlog.d(TAG, "SmsDispatchersController created");
 
         mContext = phone.getContext();
@@ -141,9 +353,9 @@
         mImsSmsDispatcher = new ImsSmsDispatcher(phone, this, ImsManager::getConnector);
         mCdmaDispatcher = new CdmaSMSDispatcher(phone, this);
         mGsmInboundSmsHandler = GsmInboundSmsHandler.makeInboundSmsHandler(phone.getContext(),
-                storageMonitor, phone);
+                storageMonitor, phone, looper);
         mCdmaInboundSmsHandler = CdmaInboundSmsHandler.makeInboundSmsHandler(phone.getContext(),
-                storageMonitor, phone, (CdmaSMSDispatcher) mCdmaDispatcher);
+                storageMonitor, phone, (CdmaSMSDispatcher) mCdmaDispatcher, looper);
         mGsmDispatcher = new GsmSMSDispatcher(phone, this, mGsmInboundSmsHandler);
         SmsBroadcastUndelivered.initialize(phone.getContext(),
                 mGsmInboundSmsHandler, mCdmaInboundSmsHandler);
@@ -188,6 +400,9 @@
         mCdmaDispatcher.dispose();
         mGsmInboundSmsHandler.dispose();
         mCdmaInboundSmsHandler.dispose();
+        // Cancels the domain selection request if it's still in progress.
+        finishDomainSelection(mDscHolder);
+        finishDomainSelection(mEmergencyDscHolder);
     }
 
     /**
@@ -242,6 +457,21 @@
         }
     }
 
+    private String getSmscAddressFromUSIMWithPhoneIdentity(String callingPkg) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            IccSmsInterfaceManager iccSmsIntMgr = mPhone.getIccSmsInterfaceManager();
+            if (iccSmsIntMgr != null) {
+                return iccSmsIntMgr.getSmscAddressFromIccEf(callingPkg);
+            } else {
+                Rlog.d(TAG, "getSmscAddressFromIccEf iccSmsIntMgr is null");
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+        return null;
+    }
+
     private void reevaluateTimerStatus() {
         long currentTime = System.currentTimeMillis();
 
@@ -390,7 +620,12 @@
         // SMS pdus when the phone is camping on CDMA(3gpp2) network and vice versa.
         android.telephony.SmsMessage msg =
                 android.telephony.SmsMessage.createFromPdu(pdu, format);
-        injectSmsPdu(msg, format, callback, false /* ignoreClass */, isOverIms);
+        injectSmsPdu(msg, format, callback, false /* ignoreClass */, isOverIms, 0 /* unused */);
+    }
+
+    @VisibleForTesting
+    public void setImsSmsDispatcher(ImsSmsDispatcher imsSmsDispatcher) {
+        mImsSmsDispatcher = imsSmsDispatcher;
     }
 
     /**
@@ -405,7 +640,7 @@
      */
     @VisibleForTesting
     public void injectSmsPdu(SmsMessage msg, String format, SmsInjectionCallback callback,
-            boolean ignoreClass, boolean isOverIms) {
+            boolean ignoreClass, boolean isOverIms, int token) {
         Rlog.d(TAG, "SmsDispatchersController:injectSmsPdu");
         try {
             if (msg == null) {
@@ -427,7 +662,7 @@
                 Rlog.i(TAG, "SmsDispatchersController:injectSmsText Sending msg=" + msg
                         + ", format=" + format + "to mGsmInboundSmsHandler");
                 mGsmInboundSmsHandler.sendMessage(
-                        InboundSmsHandler.EVENT_INJECT_SMS, isOverIms ? 1 : 0, 0, ar);
+                        InboundSmsHandler.EVENT_INJECT_SMS, isOverIms ? 1 : 0, token, ar);
             } else if (format.equals(SmsConstants.FORMAT_3GPP2)) {
                 Rlog.i(TAG, "SmsDispatchersController:injectSmsText Sending msg=" + msg
                         + ", format=" + format + "to mCdmaInboundSmsHandler");
@@ -445,20 +680,60 @@
     }
 
     /**
+     * sets ImsManager object.
+     *
+     * @param imsManager holds a valid object or a null for setting
+     */
+    public boolean setImsManager(ImsManager imsManager) {
+        if (mGsmInboundSmsHandler != null) {
+            mGsmInboundSmsHandler.setImsManager(imsManager);
+            return true;
+        }
+        return false;
+    }
+
+    /**
      * Retry the message along to the radio.
      *
      * @param tracker holds the SMS message to send
      */
     public void sendRetrySms(SMSDispatcher.SmsTracker tracker) {
-        String oldFormat = tracker.mFormat;
         boolean retryUsingImsService = false;
 
-        if (!tracker.mUsesImsServiceForIms && mImsSmsDispatcher.isAvailable()) {
-            // If this tracker has not been handled by ImsSmsDispatcher yet and IMS Service is
-            // available now, retry this failed tracker using IMS Service.
-            retryUsingImsService = true;
+        if (!tracker.mUsesImsServiceForIms) {
+            if (mDomainSelectionResolverProxy.isDomainSelectionSupported()) {
+                DomainSelectionConnectionHolder holder = getDomainSelectionConnection(false);
+
+                // If the DomainSelectionConnection is not available,
+                // fallback to the legacy implementation.
+                if (holder != null && holder.getConnection() != null) {
+                    sendSmsUsingDomainSelection(holder,
+                            new PendingRequest(PendingRequest.TYPE_RETRY_SMS, tracker,
+                                    null, null, null, null, null, false, null, 0, null, null, false,
+                                    0, false, 0, 0L, false),
+                            "sendRetrySms");
+                    return;
+                }
+            }
+
+            if (mImsSmsDispatcher.isAvailable()) {
+                // If this tracker has not been handled by ImsSmsDispatcher yet and IMS Service is
+                // available now, retry this failed tracker using IMS Service.
+                retryUsingImsService = true;
+            }
         }
 
+        sendRetrySms(tracker, retryUsingImsService);
+    }
+
+    /**
+     * Retry the message along to the radio.
+     *
+     * @param tracker holds the SMS message to send
+     * @param retryUsingImsService a flag to indicate whether the retry SMS can use the ImsService
+     */
+    public void sendRetrySms(SMSDispatcher.SmsTracker tracker, boolean retryUsingImsService) {
+        String oldFormat = tracker.mFormat;
         // If retryUsingImsService is true, newFormat will be IMS SMS format. Otherwise, newFormat
         // will be based on voice technology.
         String newFormat =
@@ -545,6 +820,23 @@
     }
 
     /**
+     * Memory Available Event
+     * @param result callback message
+     */
+    public void reportSmsMemoryStatus(Message result) {
+        Rlog.d(TAG, "reportSmsMemoryStatus: ");
+        try {
+            mImsSmsDispatcher.onMemoryAvailable();
+            AsyncResult.forMessage(result, null, null);
+            result.sendToTarget();
+        } catch (Exception e) {
+            Rlog.e(TAG, "reportSmsMemoryStatus Failed ", e);
+            AsyncResult.forMessage(result, null, e);
+            result.sendToTarget();
+        }
+    }
+
+    /**
      * SMS over IMS is supported if IMS is registered and SMS is supported on IMS.
      *
      * @return true if SMS over IMS is supported via an IMS Service or mIms is true for the older
@@ -590,6 +882,347 @@
         return (mCdmaDispatcher.getFormat().equals(format));
     }
 
+    /** Sets a proxy interface for accessing the methods of {@link DomainSelectionResolver}. */
+    @VisibleForTesting
+    public void setDomainSelectionResolverProxy(@NonNull DomainSelectionResolverProxy proxy) {
+        mDomainSelectionResolverProxy = proxy;
+    }
+
+    /**
+     * Determines whether or not to use CDMA format for MO SMS when the domain selection uses.
+     * If the domain is {@link NetworkRegistrationInfo#DOMAIN_PS}, then format is based on
+     * IMS SMS format, otherwise format is based on current phone type.
+     *
+     * @return {@code true} if CDMA format should be used for MO SMS, {@code false} otherwise.
+     */
+    private boolean isCdmaMo(@NetworkRegistrationInfo.Domain int domain) {
+        if (domain != NetworkRegistrationInfo.DOMAIN_PS) {
+            // IMS is not registered, use voice technology to determine SMS format.
+            return (PhoneConstants.PHONE_TYPE_CDMA == mPhone.getPhoneType());
+        }
+        // IMS is registered with SMS support
+        return isCdmaFormat(mImsSmsDispatcher.getFormat());
+    }
+
+    /**
+     * Returns a {@link DomainSelectionConnectionHolder} according to the flag specified.
+     *
+     * @param emergency The flag to indicate that the domain selection is for an emergency SMS.
+     * @return A {@link DomainSelectionConnectionHolder} instance or null.
+     */
+    @VisibleForTesting
+    @Nullable
+    protected DomainSelectionConnectionHolder getDomainSelectionConnectionHolder(
+            boolean emergency) {
+        return emergency ? mEmergencyDscHolder : mDscHolder;
+    }
+
+    /**
+     * Returns a {@link DomainSelectionConnectionHolder} if the domain selection supports,
+     * return null otherwise.
+     *
+     * @param emergency The flag to indicate that the domain selection is for an emergency SMS.
+     * @return A {@link DomainSelectionConnectionHolder} that grabs the
+     *         {@link DomainSelectionConnection} and its related information to use the domain
+     *         selection architecture.
+     */
+    private DomainSelectionConnectionHolder getDomainSelectionConnection(boolean emergency) {
+        DomainSelectionConnectionHolder holder = getDomainSelectionConnectionHolder(emergency);
+        DomainSelectionConnection connection = (holder != null) ? holder.getConnection() : null;
+        boolean created = false;
+
+        if (connection == null) {
+            connection = mDomainSelectionResolverProxy.getDomainSelectionConnection(
+                    mPhone, DomainSelectionService.SELECTOR_TYPE_SMS, emergency);
+
+            if (connection == null) {
+                // Domain selection architecture is not supported.
+                // Use the legacy architecture.
+                return null;
+            }
+
+            created = true;
+        }
+
+        if (holder == null) {
+            holder = new DomainSelectionConnectionHolder(emergency);
+
+            if (emergency) {
+                mEmergencyDscHolder = holder;
+            } else {
+                mDscHolder = holder;
+            }
+        }
+
+        holder.setConnection(connection);
+
+        return holder;
+    }
+
+    /**
+     * Requests the domain selection for MO SMS.
+     *
+     * @param holder The {@link DomainSelectionConnectionHolder} that contains the
+     *               {@link DomainSelectionConnection} and its related information.
+     */
+    private void requestDomainSelection(@NonNull DomainSelectionConnectionHolder holder) {
+        DomainSelectionService.SelectionAttributes attr =
+                new DomainSelectionService.SelectionAttributes.Builder(mPhone.getPhoneId(),
+                        mPhone.getSubId(), DomainSelectionService.SELECTOR_TYPE_SMS)
+                .setEmergency(holder.isEmergency())
+                .build();
+
+        if (holder.isEmergency()) {
+            EmergencySmsDomainSelectionConnection emergencyConnection =
+                    (EmergencySmsDomainSelectionConnection) holder.getConnection();
+            CompletableFuture<Integer> future =
+                    emergencyConnection.requestDomainSelection(attr, holder);
+            future.thenAcceptAsync((domain) -> {
+                if (VDBG) {
+                    logd("requestDomainSelection(emergency): domain="
+                            + DomainSelectionService.getDomainName(domain));
+                }
+                sendAllPendingRequests(holder, domain);
+                finishDomainSelection(holder);
+            }, this::post);
+        } else {
+            SmsDomainSelectionConnection connection =
+                    (SmsDomainSelectionConnection) holder.getConnection();
+            CompletableFuture<Integer> future = connection.requestDomainSelection(attr, holder);
+            future.thenAcceptAsync((domain) -> {
+                if (VDBG) {
+                    logd("requestDomainSelection: domain="
+                            + DomainSelectionService.getDomainName(domain));
+                }
+                sendAllPendingRequests(holder, domain);
+                finishDomainSelection(holder);
+            }, this::post);
+        }
+    }
+
+    /**
+     * Sends a SMS after selecting the domain via the domain selection service.
+     *
+     * @param holder The {@link DomainSelectionConnectionHolder} that contains the
+     *               {@link DomainSelectionConnection} and its related information.
+     * @param request The {@link PendingRequest} that stores the SMS request
+     *                (data, text, multipart text) to be sent.
+     * @param logTag The log tag to display which method called this method.
+     */
+    private void sendSmsUsingDomainSelection(@NonNull DomainSelectionConnectionHolder holder,
+            @NonNull PendingRequest request, @NonNull String logTag) {
+        boolean isDomainSelectionRequested = holder.isDomainSelectionRequested();
+        // The domain selection is in progress so waits for the result of
+        // the domain selection by adding this request to the pending list.
+        holder.addRequest(request);
+
+        if (!isDomainSelectionRequested) {
+            if (VDBG) {
+                logd("requestDomainSelection: " + logTag);
+            }
+            requestDomainSelection(holder);
+        }
+    }
+
+    /**
+     * Finishes the domain selection for MO SMS.
+     *
+     * @param holder The {@link DomainSelectionConnectionHolder} object that is being finished.
+     */
+    private void finishDomainSelection(DomainSelectionConnectionHolder holder) {
+        DomainSelectionConnection connection = (holder != null) ? holder.getConnection() : null;
+
+        if (connection != null) {
+            // After this method is called, the domain selection service will clean up
+            // its resources and finish the procedure that are related to the current domain
+            // selection request.
+            connection.finishSelection();
+        }
+
+        if (holder != null) {
+            final List<PendingRequest> pendingRequests = holder.getPendingRequests();
+
+            logd("finishDomainSelection: pendingRequests=" + pendingRequests.size());
+
+            for (PendingRequest r : pendingRequests) {
+                triggerSentIntentForFailure(r.sentIntents);
+            }
+
+            holder.clearAllRequests();
+            holder.setConnection(null);
+        }
+    }
+
+    /**
+     * Notifies the application that MO SMS is not sent by the error of domain selection.
+     *
+     * @param holder The {@link DomainSelectionConnectionHolder} object that is being terminated.
+     */
+    private void notifyDomainSelectionTerminated(@NonNull DomainSelectionConnectionHolder holder) {
+        final List<PendingRequest> pendingRequests = holder.getPendingRequests();
+
+        logd("notifyDomainSelectionTerminated: pendingRequests=" + pendingRequests.size());
+
+        for (PendingRequest r : pendingRequests) {
+            triggerSentIntentForFailure(r.sentIntents);
+        }
+
+        holder.setConnection(null);
+        holder.clearAllRequests();
+    }
+
+    /**
+     * Sends all pending requests for MO SMS.
+     *
+     * @param holder The {@link DomainSelectionConnectionHolder} object that all the pending
+     *               requests are handled.
+     * @param domain The domain where the SMS is being sent, which can be one of the following:
+     *               - {@link NetworkRegistrationInfo#DOMAIN_PS}
+     *               - {@link NetworkRegistrationInfo#DOMAIN_CS}
+     */
+    private void sendAllPendingRequests(@NonNull DomainSelectionConnectionHolder holder,
+            @NetworkRegistrationInfo.Domain int domain) {
+        final List<PendingRequest> pendingRequests = holder.getPendingRequests();
+
+        if (VDBG) {
+            logd("sendAllPendingRequests: domain=" + DomainSelectionService.getDomainName(domain)
+                    + ", size=" + pendingRequests.size());
+        }
+
+        for (PendingRequest r : pendingRequests) {
+            switch (r.type) {
+                case PendingRequest.TYPE_DATA:
+                    sendData(domain, r);
+                    break;
+                case PendingRequest.TYPE_TEXT:
+                    sendText(domain, r);
+                    break;
+                case PendingRequest.TYPE_MULTIPART_TEXT:
+                    sendMultipartText(domain, r);
+                    break;
+                case PendingRequest.TYPE_RETRY_SMS:
+                    sendRetrySms(r.tracker, (domain == NetworkRegistrationInfo.DOMAIN_PS));
+                    break;
+                default:
+                    // Not reachable.
+                    break;
+            }
+        }
+
+        holder.clearAllRequests();
+    }
+
+    /**
+     * Sends a data based SMS to a specific application port.
+     *
+     * @param domain The domain where the SMS is being sent, which can be one of the following:
+     *               - {@link NetworkRegistrationInfo#DOMAIN_PS}
+     *               - {@link NetworkRegistrationInfo#DOMAIN_CS}
+     * @param request The pending request for MO SMS.
+     */
+    private void sendData(@NetworkRegistrationInfo.Domain int domain,
+            @NonNull PendingRequest request) {
+        if (domain == NetworkRegistrationInfo.DOMAIN_PS) {
+            mImsSmsDispatcher.sendData(request.callingPackage, request.destAddr, request.scAddr,
+                    request.destPort, request.data, request.sentIntents.get(0),
+                    request.deliveryIntents.get(0), request.isForVvm);
+        } else if (isCdmaMo(domain)) {
+            mCdmaDispatcher.sendData(request.callingPackage, request.destAddr, request.scAddr,
+                    request.destPort, request.data, request.sentIntents.get(0),
+                    request.deliveryIntents.get(0), request.isForVvm);
+        } else {
+            mGsmDispatcher.sendData(request.callingPackage, request.destAddr, request.scAddr,
+                    request.destPort, request.data, request.sentIntents.get(0),
+                    request.deliveryIntents.get(0), request.isForVvm);
+        }
+    }
+
+    /**
+     * Sends a text based SMS.
+     *
+     * @param domain The domain where the SMS is being sent, which can be one of the following:
+     *               - {@link NetworkRegistrationInfo#DOMAIN_PS}
+     *               - {@link NetworkRegistrationInfo#DOMAIN_CS}
+     * @param request The pending request for MO SMS.
+     */
+    private void sendText(@NetworkRegistrationInfo.Domain int domain,
+            @NonNull PendingRequest request) {
+        if (domain == NetworkRegistrationInfo.DOMAIN_PS) {
+            mImsSmsDispatcher.sendText(request.destAddr, request.scAddr, request.texts.get(0),
+                    request.sentIntents.get(0), request.deliveryIntents.get(0),
+                    request.messageUri, request.callingPackage, request.persistMessage,
+                    request.priority, false /*request.expectMore*/, request.validityPeriod,
+                    request.isForVvm, request.messageId, request.skipShortCodeCheck);
+        } else {
+            if (isCdmaMo(domain)) {
+                mCdmaDispatcher.sendText(request.destAddr, request.scAddr, request.texts.get(0),
+                        request.sentIntents.get(0), request.deliveryIntents.get(0),
+                        request.messageUri, request.callingPackage, request.persistMessage,
+                        request.priority, request.expectMore, request.validityPeriod,
+                        request.isForVvm, request.messageId, request.skipShortCodeCheck);
+            } else {
+                mGsmDispatcher.sendText(request.destAddr, request.scAddr, request.texts.get(0),
+                        request.sentIntents.get(0), request.deliveryIntents.get(0),
+                        request.messageUri, request.callingPackage, request.persistMessage,
+                        request.priority, request.expectMore, request.validityPeriod,
+                        request.isForVvm, request.messageId, request.skipShortCodeCheck);
+            }
+        }
+    }
+
+    /**
+     * Sends a multi-part text based SMS.
+     *
+     * @param domain The domain where the SMS is being sent, which can be one of the following:
+     *               - {@link NetworkRegistrationInfo#DOMAIN_PS}
+     *               - {@link NetworkRegistrationInfo#DOMAIN_CS}
+     * @param request The pending request for MO SMS.
+     */
+    private void sendMultipartText(@NetworkRegistrationInfo.Domain int domain,
+            @NonNull PendingRequest request) {
+        if (domain == NetworkRegistrationInfo.DOMAIN_PS) {
+            mImsSmsDispatcher.sendMultipartText(request.destAddr, request.scAddr, request.texts,
+                    request.sentIntents, request.deliveryIntents, request.messageUri,
+                    request.callingPackage, request.persistMessage, request.priority,
+                    false /*request.expectMore*/, request.validityPeriod, request.messageId);
+        } else {
+            if (isCdmaMo(domain)) {
+                mCdmaDispatcher.sendMultipartText(request.destAddr, request.scAddr, request.texts,
+                        request.sentIntents, request.deliveryIntents, request.messageUri,
+                        request.callingPackage, request.persistMessage, request.priority,
+                        request.expectMore, request.validityPeriod, request.messageId);
+            } else {
+                mGsmDispatcher.sendMultipartText(request.destAddr, request.scAddr, request.texts,
+                        request.sentIntents, request.deliveryIntents, request.messageUri,
+                        request.callingPackage, request.persistMessage, request.priority,
+                        request.expectMore, request.validityPeriod, request.messageId);
+            }
+        }
+    }
+
+    private void triggerSentIntentForFailure(@NonNull PendingIntent sentIntent) {
+        try {
+            sentIntent.send(SmsManager.RESULT_ERROR_GENERIC_FAILURE);
+        } catch (CanceledException e) {
+            logd("Intent has been canceled!");
+        }
+    }
+
+    private void triggerSentIntentForFailure(@NonNull List<PendingIntent> sentIntents) {
+        for (PendingIntent sentIntent : sentIntents) {
+            triggerSentIntentForFailure(sentIntent);
+        }
+    }
+
+    /**
+     * Creates an ArrayList object from any object.
+     */
+    private static <T> ArrayList<T> asArrayList(T object) {
+        ArrayList<T> list = new ArrayList<>();
+        list.add(object);
+        return list;
+    }
+
     /**
      * Send a data based SMS to a specific application port.
      *
@@ -671,6 +1304,26 @@
      */
     protected void sendData(String callingPackage, String destAddr, String scAddr, int destPort,
             byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent, boolean isForVvm) {
+        if (TextUtils.isEmpty(scAddr)) {
+            scAddr = getSmscAddressFromUSIMWithPhoneIdentity(callingPackage);
+        }
+
+        if (mDomainSelectionResolverProxy.isDomainSelectionSupported()) {
+            DomainSelectionConnectionHolder holder = getDomainSelectionConnection(false);
+
+            // If the DomainSelectionConnection is not available,
+            // fallback to the legacy implementation.
+            if (holder != null && holder.getConnection() != null) {
+                sendSmsUsingDomainSelection(holder,
+                        new PendingRequest(PendingRequest.TYPE_DATA, null, callingPackage,
+                                destAddr, scAddr, asArrayList(sentIntent),
+                                asArrayList(deliveryIntent), isForVvm, data, destPort, null, null,
+                                false, 0, false, 0, 0L, false),
+                        "sendData");
+                return;
+            }
+        }
+
         if (mImsSmsDispatcher.isAvailable()) {
             mImsSmsDispatcher.sendData(callingPackage, destAddr, scAddr, destPort, data, sentIntent,
                     deliveryIntent, isForVvm);
@@ -784,19 +1437,148 @@
             PendingIntent deliveryIntent, Uri messageUri, String callingPkg, boolean persistMessage,
             int priority, boolean expectMore, int validityPeriod, boolean isForVvm,
             long messageId) {
+        sendText(destAddr, scAddr, text, sentIntent, deliveryIntent, messageUri, callingPkg,
+                persistMessage, priority, expectMore, validityPeriod, isForVvm, messageId, false);
+    }
+
+    /**
+     * Send a text based SMS.
+     *
+     * @param destAddr the address to send the message to
+     * @param scAddr is the service center address or null to use
+     *  the current default SMSC
+     * @param text the body of the message to send
+     * @param sentIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is successfully sent, or failed.
+     *  The result code will be <code>Activity.RESULT_OK<code> for success,
+     *  or one of these errors:<br>
+     *  <code>SmsManager.RESULT_ERROR_GENERIC_FAILURE</code><br>
+     *  <code>SmsManager.RESULT_ERROR_RADIO_OFF</code><br>
+     *  <code>SmsManager.RESULT_ERROR_NULL_PDU</code><br>
+     *  <code>SmsManager.RESULT_ERROR_NO_SERVICE</code><br>
+     *  <code>SmsManager.RESULT_ERROR_LIMIT_EXCEEDED</code><br>
+     *  <code>SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE</code><br>
+     *  <code>SmsManager.RESULT_ERROR_SHORT_CODE_NOT_ALLOWED</code><br>
+     *  <code>SmsManager.RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED</code><br>
+     *  <code>SmsManager.RESULT_RADIO_NOT_AVAILABLE</code><br>
+     *  <code>SmsManager.RESULT_NETWORK_REJECT</code><br>
+     *  <code>SmsManager.RESULT_INVALID_ARGUMENTS</code><br>
+     *  <code>SmsManager.RESULT_INVALID_STATE</code><br>
+     *  <code>SmsManager.RESULT_NO_MEMORY</code><br>
+     *  <code>SmsManager.RESULT_INVALID_SMS_FORMAT</code><br>
+     *  <code>SmsManager.RESULT_SYSTEM_ERROR</code><br>
+     *  <code>SmsManager.RESULT_MODEM_ERROR</code><br>
+     *  <code>SmsManager.RESULT_NETWORK_ERROR</code><br>
+     *  <code>SmsManager.RESULT_ENCODING_ERROR</code><br>
+     *  <code>SmsManager.RESULT_INVALID_SMSC_ADDRESS</code><br>
+     *  <code>SmsManager.RESULT_OPERATION_NOT_ALLOWED</code><br>
+     *  <code>SmsManager.RESULT_INTERNAL_ERROR</code><br>
+     *  <code>SmsManager.RESULT_NO_RESOURCES</code><br>
+     *  <code>SmsManager.RESULT_CANCELLED</code><br>
+     *  <code>SmsManager.RESULT_REQUEST_NOT_SUPPORTED</code><br>
+     *  <code>SmsManager.RESULT_NO_BLUETOOTH_SERVICE</code><br>
+     *  <code>SmsManager.RESULT_INVALID_BLUETOOTH_ADDRESS</code><br>
+     *  <code>SmsManager.RESULT_BLUETOOTH_DISCONNECTED</code><br>
+     *  <code>SmsManager.RESULT_UNEXPECTED_EVENT_STOP_SENDING</code><br>
+     *  <code>SmsManager.RESULT_SMS_BLOCKED_DURING_EMERGENCY</code><br>
+     *  <code>SmsManager.RESULT_SMS_SEND_RETRY_FAILED</code><br>
+     *  <code>SmsManager.RESULT_REMOTE_EXCEPTION</code><br>
+     *  <code>SmsManager.RESULT_NO_DEFAULT_SMS_APP</code><br>
+     *  <code>SmsManager.RESULT_RIL_RADIO_NOT_AVAILABLE</code><br>
+     *  <code>SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY</code><br>
+     *  <code>SmsManager.RESULT_RIL_NETWORK_REJECT</code><br>
+     *  <code>SmsManager.RESULT_RIL_INVALID_STATE</code><br>
+     *  <code>SmsManager.RESULT_RIL_INVALID_ARGUMENTS</code><br>
+     *  <code>SmsManager.RESULT_RIL_NO_MEMORY</code><br>
+     *  <code>SmsManager.RESULT_RIL_REQUEST_RATE_LIMITED</code><br>
+     *  <code>SmsManager.RESULT_RIL_INVALID_SMS_FORMAT</code><br>
+     *  <code>SmsManager.RESULT_RIL_SYSTEM_ERR</code><br>
+     *  <code>SmsManager.RESULT_RIL_ENCODING_ERR</code><br>
+     *  <code>SmsManager.RESULT_RIL_INVALID_SMSC_ADDRESS</code><br>
+     *  <code>SmsManager.RESULT_RIL_MODEM_ERR</code><br>
+     *  <code>SmsManager.RESULT_RIL_NETWORK_ERR</code><br>
+     *  <code>SmsManager.RESULT_RIL_INTERNAL_ERR</code><br>
+     *  <code>SmsManager.RESULT_RIL_REQUEST_NOT_SUPPORTED</code><br>
+     *  <code>SmsManager.RESULT_RIL_INVALID_MODEM_STATE</code><br>
+     *  <code>SmsManager.RESULT_RIL_NETWORK_NOT_READY</code><br>
+     *  <code>SmsManager.RESULT_RIL_OPERATION_NOT_ALLOWED</code><br>
+     *  <code>SmsManager.RESULT_RIL_NO_RESOURCES</code><br>
+     *  <code>SmsManager.RESULT_RIL_CANCELLED</code><br>
+     *  <code>SmsManager.RESULT_RIL_SIM_ABSENT</code><br>
+     *  <code>SmsManager.RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED</code><br>
+     *  <code>SmsManager.RESULT_RIL_ACCESS_BARRED</code><br>
+     *  <code>SmsManager.RESULT_RIL_BLOCKED_DUE_TO_CALL</code><br>
+     *  For <code>SmsManager.RESULT_ERROR_GENERIC_FAILURE</code> or any of the RESULT_RIL errors,
+     *  the sentIntent may include the extra "errorCode" containing a radio technology specific
+     *  value, generally only useful for troubleshooting.<br>
+     *  The per-application based SMS control checks sentIntent. If sentIntent
+     *  is NULL the caller will be checked against all unknown applications,
+     *  which cause smaller number of SMS to be sent in checking period.
+     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is delivered to the recipient.  The
+     * @param messageUri optional URI of the message if it is already stored in the system
+     * @param callingPkg the calling package name
+     * @param persistMessage whether to save the sent message into SMS DB for a
+     *  non-default SMS app.
+     * @param priority Priority level of the message
+     *  Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
+     *  ---------------------------------
+     *  PRIORITY      | Level of Priority
+     *  ---------------------------------
+     *      '00'      |     Normal
+     *      '01'      |     Interactive
+     *      '10'      |     Urgent
+     *      '11'      |     Emergency
+     *  ----------------------------------
+     *  Any Other values included Negative considered as Invalid Priority Indicator of the message.
+     * @param expectMore is a boolean to indicate the sending messages through same link or not.
+     * @param validityPeriod Validity Period of the message in mins.
+     *  Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+     *  Validity Period(Minimum) -> 5 mins
+     *  Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
+     *  Any Other values included Negative considered as Invalid Validity Period of the message.
+     * @param skipShortCodeCheck Skip check for short code type destination address.
+     */
+    public void sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent,
+            PendingIntent deliveryIntent, Uri messageUri, String callingPkg, boolean persistMessage,
+            int priority, boolean expectMore, int validityPeriod, boolean isForVvm,
+            long messageId, boolean skipShortCodeCheck) {
+        if (TextUtils.isEmpty(scAddr)) {
+            scAddr = getSmscAddressFromUSIMWithPhoneIdentity(callingPkg);
+        }
+
+        if (mDomainSelectionResolverProxy.isDomainSelectionSupported()) {
+            TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+            boolean isEmergency = tm.isEmergencyNumber(destAddr);
+            DomainSelectionConnectionHolder holder = getDomainSelectionConnection(isEmergency);
+
+            // If the DomainSelectionConnection is not available,
+            // fallback to the legacy implementation.
+            if (holder != null && holder.getConnection() != null) {
+                sendSmsUsingDomainSelection(holder,
+                        new PendingRequest(PendingRequest.TYPE_TEXT, null, callingPkg,
+                                destAddr, scAddr, asArrayList(sentIntent),
+                                asArrayList(deliveryIntent), isForVvm, null, 0, asArrayList(text),
+                                messageUri, persistMessage, priority, expectMore, validityPeriod,
+                                messageId, skipShortCodeCheck),
+                        "sendText");
+                return;
+            }
+        }
+
         if (mImsSmsDispatcher.isAvailable() || mImsSmsDispatcher.isEmergencySmsSupport(destAddr)) {
             mImsSmsDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
                     messageUri, callingPkg, persistMessage, priority, false /*expectMore*/,
-                    validityPeriod, isForVvm, messageId);
+                    validityPeriod, isForVvm, messageId, skipShortCodeCheck);
         } else {
             if (isCdmaMo()) {
                 mCdmaDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
                         messageUri, callingPkg, persistMessage, priority, expectMore,
-                        validityPeriod, isForVvm, messageId);
+                        validityPeriod, isForVvm, messageId, skipShortCodeCheck);
             } else {
                 mGsmDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
                         messageUri, callingPkg, persistMessage, priority, expectMore,
-                        validityPeriod, isForVvm, messageId);
+                        validityPeriod, isForVvm, messageId, skipShortCodeCheck);
             }
         }
     }
@@ -910,6 +1692,26 @@
             ArrayList<PendingIntent> deliveryIntents, Uri messageUri, String callingPkg,
             boolean persistMessage, int priority, boolean expectMore, int validityPeriod,
             long messageId) {
+        if (TextUtils.isEmpty(scAddr)) {
+            scAddr = getSmscAddressFromUSIMWithPhoneIdentity(callingPkg);
+        }
+
+        if (mDomainSelectionResolverProxy.isDomainSelectionSupported()) {
+            DomainSelectionConnectionHolder holder = getDomainSelectionConnection(false);
+
+            // If the DomainSelectionConnection is not available,
+            // fallback to the legacy implementation.
+            if (holder != null && holder.getConnection() != null) {
+                sendSmsUsingDomainSelection(holder,
+                        new PendingRequest(PendingRequest.TYPE_MULTIPART_TEXT, null,
+                                callingPkg, destAddr, scAddr, sentIntents, deliveryIntents, false,
+                                null, 0, parts, messageUri, persistMessage, priority, expectMore,
+                                validityPeriod, messageId, false),
+                        "sendMultipartText");
+                return;
+            }
+        }
+
         if (mImsSmsDispatcher.isAvailable()) {
             mImsSmsDispatcher.sendMultipartText(destAddr, scAddr, parts, sentIntents,
                     deliveryIntents, messageUri, callingPkg, persistMessage, priority,
@@ -1066,4 +1868,8 @@
     private void logd(String msg) {
         Rlog.d(TAG, msg);
     }
+
+    private void logi(String s) {
+        Rlog.i(TAG + " [" + mPhone.getPhoneId() + "]", s);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/SmsPermissions.java b/src/java/com/android/internal/telephony/SmsPermissions.java
index 44751ac..529eea0 100644
--- a/src/java/com/android/internal/telephony/SmsPermissions.java
+++ b/src/java/com/android/internal/telephony/SmsPermissions.java
@@ -23,9 +23,11 @@
 import android.content.Intent;
 import android.os.Binder;
 import android.os.Build;
+import android.os.UserHandle;
 import android.service.carrier.CarrierMessagingService;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.telephony.Rlog;
 
 /**
@@ -132,10 +134,19 @@
      */
     public boolean checkCallingOrSelfCanGetSmscAddress(String callingPackage, String message) {
         // Allow it to the default SMS app always.
-        if (!isCallerDefaultSmsPackage(callingPackage)) {
-            TelephonyPermissions
-                        .enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
-                                mContext, mPhone.getSubId(), message);
+        boolean isDefaultSmsPackage;
+        int callerUid = Binder.getCallingUid();
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            isDefaultSmsPackage = isCallerDefaultSmsPackage(callingPackage, callerUid);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+        if (!isDefaultSmsPackage) {
+            Rlog.d(LOG_TAG, "Caller is not a default SMS application");
+            TelephonyPermissions.
+                    enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
+                            mContext, mPhone.getSubId(), message);
         }
         return true;
     }
@@ -151,33 +162,49 @@
      */
     public boolean checkCallingOrSelfCanSetSmscAddress(String callingPackage, String message) {
         // Allow it to the default SMS app always.
-        if (!isCallerDefaultSmsPackage(callingPackage)) {
+        boolean isDefaultSmsPackage;
+        int callerUid = Binder.getCallingUid();
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            isDefaultSmsPackage = isCallerDefaultSmsPackage(callingPackage, callerUid);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+        if (!isDefaultSmsPackage) {
+            Rlog.d(LOG_TAG, "Caller is not a default SMS application");
             // Allow it with MODIFY_PHONE_STATE or Carrier Privileges
-            TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
-                    mContext, mPhone.getSubId(), message);
+            TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(mContext,
+                    mPhone.getSubId(), message);
         }
         return true;
     }
 
     /** Check if a package is default SMS app. */
     @VisibleForTesting
-    public boolean isCallerDefaultSmsPackage(String packageName) {
-        if (packageNameMatchesCallingUid(packageName)) {
-            return SmsApplication.isDefaultSmsApplication(mContext, packageName);
+    public boolean isCallerDefaultSmsPackage(String packageName, int callerUid) {
+        if (packageNameMatchesCallingUid(packageName, callerUid)) {
+            UserHandle userHandle = TelephonyUtils.getSubscriptionUserHandle(mContext,
+                    mPhone.getSubId());
+            return SmsApplication.isDefaultSmsApplicationAsUser(mContext, packageName, userHandle);
         }
         return false;
     }
 
+    @VisibleForTesting
+    public boolean packageNameMatchesCallingUid(String packageName) {
+        return packageNameMatchesCallingUid(packageName, Binder.getCallingUid());
+    }
+
     /**
      * Check if the passed in packageName belongs to the calling uid.
      * @param packageName name of the package to check
      * @return true if package belongs to calling uid, false otherwise
      */
     @VisibleForTesting
-    public boolean packageNameMatchesCallingUid(String packageName) {
+    public boolean packageNameMatchesCallingUid(String packageName, int callerUid) {
         try {
             ((AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE))
-                    .checkPackage(Binder.getCallingUid(), packageName);
+                    .checkPackage(callerUid, packageName);
             // If checkPackage doesn't throw an exception then we are the given package
             return true;
         } catch (SecurityException e) {
diff --git a/src/java/com/android/internal/telephony/SmsStorageMonitor.java b/src/java/com/android/internal/telephony/SmsStorageMonitor.java
old mode 100755
new mode 100644
index 2375c73..2736f7a
--- a/src/java/com/android/internal/telephony/SmsStorageMonitor.java
+++ b/src/java/com/android/internal/telephony/SmsStorageMonitor.java
@@ -18,18 +18,22 @@
 
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.res.Resources;
 import android.os.AsyncResult;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Message;
 import android.os.PowerManager;
+import android.os.UserHandle;
 import android.provider.Telephony.Sms.Intents;
 import android.telephony.SubscriptionManager;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.telephony.Rlog;
 
 /**
@@ -40,7 +44,7 @@
  * dual-mode devices that require support for both 3GPP and 3GPP2 format messages.
  */
 public class SmsStorageMonitor extends Handler {
-    private static final String TAG = "SmsStorageMonitor";
+    private static final String TAG = "SmsStorageMonitor1";
 
     /** Maximum number of times to retry memory status reporting */
     private static final int MAX_RETRIES = 1;
@@ -83,6 +87,10 @@
     final CommandsInterface mCi;
     boolean mStorageAvailable = true;
 
+    boolean mInitialStorageAvailableStatus = true;
+
+    private boolean mMemoryStatusOverrideFlag = false;
+
     /**
      * Hold the wake lock for 5 seconds, which should be enough time for
      * any receiver(s) to grab its own wake lock.
@@ -111,6 +119,31 @@
         mContext.registerReceiver(mResultReceiver, filter);
     }
 
+    /**
+     * Overriding of the Memory Status by the TestApi and send the event to Handler to test
+     * the RP-SMMA feature
+     * @param isStorageAvailable boolean value specifies the MemoryStatus to be
+     * sent to Handler
+     */
+    public void sendMemoryStatusOverride(boolean isStorageAvailable) {
+        if (!mMemoryStatusOverrideFlag) {
+            mInitialStorageAvailableStatus = mStorageAvailable;
+            mMemoryStatusOverrideFlag = true;
+        }
+        mStorageAvailable = isStorageAvailable;
+        if (isStorageAvailable) {
+            sendMessage(obtainMessage(EVENT_REPORT_MEMORY_STATUS));
+        }
+    }
+
+    /**
+     * reset Memory Status change made by {@link #sendMemoryStatusOverride}
+     */
+    public void clearMemoryStatusOverride() {
+        mStorageAvailable = mInitialStorageAvailableStatus;
+        mMemoryStatusOverrideFlag = false;
+    }
+
     @VisibleForTesting
     public void setMaxRetries(int maxCount) {
         mMaxRetryCount = maxCount;
@@ -209,6 +242,18 @@
 
     private void sendMemoryStatusReport(boolean isAvailable) {
         mIsWaitingResponse = true;
+        Resources r = mContext.getResources();
+        if (r.getBoolean(com.android.internal.R.bool.config_smma_notification_supported_over_ims)) {
+            IccSmsInterfaceManager smsIfcMngr = mPhone.getIccSmsInterfaceManager();
+            if (smsIfcMngr != null) {
+                Rlog.d(TAG, "sendMemoryStatusReport: smsIfcMngr is available");
+                if (smsIfcMngr.mDispatchersController.isIms() && isAvailable) {
+                    smsIfcMngr.mDispatchersController.reportSmsMemoryStatus(
+                            obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE));
+                    return;
+                }
+            }
+        }
         mCi.reportSmsMemoryStatus(isAvailable, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE));
     }
 
@@ -223,9 +268,14 @@
      * that SIM storage for SMS messages is full.
      */
     private void handleIccFull() {
+        UserHandle userHandle = TelephonyUtils.getSubscriptionUserHandle(mContext,
+                mPhone.getSubId());
+        ComponentName componentName = SmsApplication.getDefaultSimFullApplicationAsUser(mContext,
+                false, userHandle);
+
         // broadcast SIM_FULL intent
         Intent intent = new Intent(Intents.SIM_FULL_ACTION);
-        intent.setComponent(SmsApplication.getDefaultSimFullApplication(mContext, false));
+        intent.setComponent(componentName);
         mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
         SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
         mContext.sendBroadcast(intent, android.Manifest.permission.RECEIVE_SMS);
diff --git a/src/java/com/android/internal/telephony/SmsUsageMonitor.java b/src/java/com/android/internal/telephony/SmsUsageMonitor.java
index 8bcdc07..8e4ac60 100644
--- a/src/java/com/android/internal/telephony/SmsUsageMonitor.java
+++ b/src/java/com/android/internal/telephony/SmsUsageMonitor.java
@@ -43,6 +43,7 @@
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
 
+import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -73,6 +74,8 @@
 
     private static final String SHORT_CODE_PATH = "/data/misc/sms/codes";
 
+    private static final String SHORT_CODE_VERSION_PATH = "/data/misc/sms/metadata/version";
+
     /** Default checking period for SMS sent without user permission. */
     private static final int DEFAULT_SMS_CHECK_PERIOD = 60000;      // 1 minute
 
@@ -128,6 +131,8 @@
     /** Last modified time for pattern file */
     private long mPatternFileLastModified = 0;
 
+    private int mPatternFileVersion = -1;
+
     private RoleManager mRoleManager;
 
     /** Directory for per-app SMS permission XML file. */
@@ -415,9 +420,11 @@
                     if (mPatternFile.exists()) {
                         if (DBG) Rlog.d(TAG, "Loading SMS Short Code patterns from file");
                         mCurrentPatternMatcher = getPatternMatcherFromFile(countryIso);
+                        mPatternFileVersion = getPatternFileVersionFromFile();
                     } else {
                         if (DBG) Rlog.d(TAG, "Loading SMS Short Code patterns from resource");
                         mCurrentPatternMatcher = getPatternMatcherFromResource(countryIso);
+                        mPatternFileVersion = -1;
                     }
                     mCurrentCountry = countryIso;
                 }
@@ -655,6 +662,37 @@
         return false;
     }
 
+    private int getPatternFileVersionFromFile() {
+        File versionFile = new File(SHORT_CODE_VERSION_PATH);
+        if (versionFile.exists()) {
+            BufferedReader reader = null;
+            try {
+                reader = new BufferedReader(new FileReader(versionFile));
+                String version = reader.readLine();
+                if (version != null) {
+                    return Integer.parseInt(version);
+                }
+            } catch (IOException e) {
+                Rlog.e(TAG, "File reader exception reading short code "
+                        + "pattern file version", e);
+            } finally {
+                try {
+                    if (reader != null) {
+                        reader.close();
+                    }
+                } catch (IOException e) {
+                    Rlog.e(TAG, "File reader exception closing short code "
+                            + "pattern file version reader", e);
+                }
+            }
+        }
+        return -1;
+    }
+
+    public int getShortCodeXmlFileVersion() {
+        return mPatternFileVersion;
+    }
+
     private static void log(String msg) {
         Rlog.d(TAG, msg);
     }
diff --git a/src/java/com/android/internal/telephony/SrvccConnection.java b/src/java/com/android/internal/telephony/SrvccConnection.java
new file mode 100644
index 0000000..f25a15c
--- /dev/null
+++ b/src/java/com/android/internal/telephony/SrvccConnection.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2022 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 android.telephony.PreciseCallState.PRECISE_CALL_STATE_ACTIVE;
+import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_ALERTING;
+import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_DIALING;
+import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_HOLDING;
+import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_INCOMING;
+import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_INCOMING_SETUP;
+import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_WAITING;
+
+import android.net.Uri;
+import android.telephony.Annotation.PreciseCallStates;
+import android.telephony.ims.ImsCallProfile;
+import android.telephony.ims.ImsStreamMediaProfile;
+import android.text.TextUtils;
+
+import com.android.ims.internal.ConferenceParticipant;
+import com.android.internal.telephony.imsphone.ImsPhoneConnection;
+import com.android.telephony.Rlog;
+
+/**
+ * Connection information for SRVCC
+ */
+public class SrvccConnection {
+    private static final String TAG = "SrvccConnection";
+
+    public static final int CALL_TYPE_NORMAL = 0;
+    public static final int CALL_TYPE_EMERGENCY = 1;
+
+    public static final int SUBSTATE_NONE = 0;
+    /** Pre-alerting state. Applicable for MT calls only */
+    public static final int SUBSTATE_PREALERTING = 1;
+
+    public static final int TONE_NONE = 0;
+    public static final int TONE_LOCAL = 1;
+    public static final int TONE_NETWORK = 2;
+
+    /** Values are CALL_TYPE_ */
+    private int mType = CALL_TYPE_NORMAL;
+
+    /** Values are Call.State */
+    private Call.State mState;
+
+    /** Values are SUBSTATE_ */
+    private int mSubstate = SUBSTATE_NONE;
+
+    /** Values are TONE_ */
+    private int mRingbackToneType = TONE_NONE;
+
+    /** true if it is a multi-party call */
+    private boolean mIsMpty = false;
+
+    /** true if it is a mobile terminated call */
+    private boolean mIsMT;
+
+    /** Remote party nummber */
+    private String mNumber;
+
+    /** Values are PhoneConstants.PRESENTATION_ */
+    private int mNumPresentation;
+
+    /** Remote party name */
+    private String mName;
+
+    /** Values are PhoneConstants.PRESENTATION_ */
+    private int mNamePresentation;
+
+    public SrvccConnection(ImsCallProfile profile,
+            ImsPhoneConnection c, @PreciseCallStates int preciseCallState) {
+        mState = toCallState(preciseCallState);
+        if (mState == Call.State.ALERTING) {
+            mRingbackToneType = isLocalTone(profile) ? TONE_LOCAL : TONE_NETWORK;
+        }
+        if (preciseCallState == PRECISE_CALL_STATE_INCOMING_SETUP) {
+            mSubstate = SUBSTATE_PREALERTING;
+        }
+
+        if (c == null) {
+            initialize(profile);
+        } else {
+            initialize(c);
+        }
+    }
+
+    public SrvccConnection(ConferenceParticipant cp, @PreciseCallStates int preciseCallState) {
+        Rlog.d(TAG, "initialize with ConferenceParticipant");
+        mState = toCallState(preciseCallState);
+        mIsMT = cp.getCallDirection() == android.telecom.Call.Details.DIRECTION_INCOMING;
+        mNumber = getParticipantAddress(cp.getHandle());
+        mNumPresentation = cp.getParticipantPresentation();
+        if (mNumPresentation == PhoneConstants.PRESENTATION_RESTRICTED) {
+            mNumber = "";
+        }
+        mName = cp.getDisplayName();
+        if (!TextUtils.isEmpty(mName)) {
+            mNamePresentation = PhoneConstants.PRESENTATION_ALLOWED;
+        } else {
+            mNamePresentation = PhoneConstants.PRESENTATION_UNKNOWN;
+        }
+        mIsMpty = true;
+    }
+
+    private static String getParticipantAddress(Uri address) {
+        if (address == null) {
+            return null;
+        }
+
+        String number = address.getSchemeSpecificPart();
+        if (TextUtils.isEmpty(number)) {
+            return null;
+        }
+
+        String[] numberParts = number.split("[@;:]");
+        if (numberParts.length == 0) return null;
+
+        return numberParts[0];
+    }
+
+    // MT call in alerting or prealerting state
+    private void initialize(ImsCallProfile profile) {
+        Rlog.d(TAG, "initialize with ImsCallProfile");
+        mIsMT = true;
+        mNumber = profile.getCallExtra(ImsCallProfile.EXTRA_OI);
+        mName = profile.getCallExtra(ImsCallProfile.EXTRA_CNA);
+        mNumPresentation = ImsCallProfile.OIRToPresentation(
+                profile.getCallExtraInt(ImsCallProfile.EXTRA_OIR));
+        mNamePresentation = ImsCallProfile.OIRToPresentation(
+                profile.getCallExtraInt(ImsCallProfile.EXTRA_CNAP));
+    }
+
+    private void initialize(ImsPhoneConnection c) {
+        Rlog.d(TAG, "initialize with ImsPhoneConnection");
+        if (c.isEmergencyCall()) {
+            mType = CALL_TYPE_EMERGENCY;
+        }
+        mIsMT = c.isIncoming();
+        mNumber = c.getAddress();
+        mNumPresentation = c.getNumberPresentation();
+        mName = c.getCnapName();
+        mNamePresentation = c.getCnapNamePresentation();
+    }
+
+    private boolean isLocalTone(ImsCallProfile profile) {
+        if (profile == null) return false;
+
+        ImsStreamMediaProfile mediaProfile = profile.getMediaProfile();
+        if (mediaProfile == null) return false;
+
+        boolean shouldPlayRingback =
+                (mediaProfile.getAudioDirection() == ImsStreamMediaProfile.DIRECTION_INACTIVE)
+                        ? true : false;
+        return shouldPlayRingback;
+    }
+
+    private static Call.State toCallState(int preciseCallState) {
+        switch (preciseCallState) {
+            case PRECISE_CALL_STATE_ACTIVE: return Call.State.ACTIVE;
+            case PRECISE_CALL_STATE_HOLDING: return Call.State.HOLDING;
+            case PRECISE_CALL_STATE_DIALING: return Call.State.DIALING;
+            case PRECISE_CALL_STATE_ALERTING: return Call.State.ALERTING;
+            case PRECISE_CALL_STATE_INCOMING: return Call.State.INCOMING;
+            case PRECISE_CALL_STATE_WAITING: return Call.State.WAITING;
+            case PRECISE_CALL_STATE_INCOMING_SETUP: return Call.State.INCOMING;
+            default:
+        }
+        return Call.State.DISCONNECTED;
+    }
+
+    /** Returns the type of the call */
+    public int getType() {
+        return mType;
+    }
+
+    /** Returns the state */
+    public Call.State getState() {
+        return mState;
+    }
+
+    /** Updates the state */
+    public void setState(Call.State state) {
+        mState = state;
+    }
+
+    /** Returns the sub state */
+    public int getSubState() {
+        return mSubstate;
+    }
+
+    /** Returns the ringback tone type */
+    public int getRingbackToneType() {
+        return mRingbackToneType;
+    }
+
+    /** true if it is a multi-party call */
+    public boolean isMultiParty() {
+        return mIsMpty;
+    }
+
+    /** true if it is a mobile terminated call */
+    public boolean isIncoming() {
+        return mIsMT;
+    }
+
+    /** Returns the remote party nummber */
+    public String getNumber() {
+        return mNumber;
+    }
+
+    /** Returns the number presentation */
+    public int getNumberPresentation() {
+        return mNumPresentation;
+    }
+
+    /** Returns the remote party name */
+    public String getName() {
+        return mName;
+    }
+
+    /** Returns the name presentation */
+    public int getNamePresentation() {
+        return mNamePresentation;
+    }
+
+    /**
+     * Build a human representation of a connection instance, suitable for debugging.
+     * Don't log personal stuff unless in debug mode.
+     * @return a string representing the internal state of this connection.
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(" type:").append(getType());
+        sb.append(", state:").append(getState());
+        sb.append(", subState:").append(getSubState());
+        sb.append(", toneType:").append(getRingbackToneType());
+        sb.append(", mpty:").append(isMultiParty());
+        sb.append(", incoming:").append(isIncoming());
+        sb.append(", numberPresentation:").append(getNumberPresentation());
+        sb.append(", number:").append(Rlog.pii(TAG, getNumber()));
+        sb.append(", namePresentation:").append(getNamePresentation());
+        sb.append(", name:").append(Rlog.pii(TAG, getName()));
+        return sb.toString();
+    }
+}
diff --git a/src/java/com/android/internal/telephony/SubscriptionController.java b/src/java/com/android/internal/telephony/SubscriptionController.java
deleted file mode 100644
index b05e600..0000000
--- a/src/java/com/android/internal/telephony/SubscriptionController.java
+++ /dev/null
@@ -1,5020 +0,0 @@
-/*
-* Copyright (C) 2014 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 android.Manifest.permission.READ_PHONE_NUMBERS;
-import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.telephony.TelephonyManager.MULTISIM_ALLOWED;
-import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION;
-import static android.telephony.UiccSlotInfo.CARD_STATE_INFO_PRESENT;
-
-import android.Manifest;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
-import android.app.AppOpsManager;
-import android.app.PendingIntent;
-import android.app.compat.CompatChanges;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
-import android.compat.annotation.UnsupportedAppUsage;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.ParcelUuid;
-import android.os.PersistableBundle;
-import android.os.RegistrantList;
-import android.os.RemoteException;
-import android.os.TelephonyServiceManager.ServiceRegisterer;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.provider.Telephony.SimInfo;
-import android.telecom.PhoneAccountHandle;
-import android.telecom.TelecomManager;
-import android.telephony.AnomalyReporter;
-import android.telephony.CarrierConfigManager;
-import android.telephony.RadioAccessFamily;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.telephony.SubscriptionManager.SimDisplayNameSource;
-import android.telephony.SubscriptionManager.UsageSetting;
-import android.telephony.TelephonyFrameworkInitializer;
-import android.telephony.TelephonyManager;
-import android.telephony.TelephonyRegistryManager;
-import android.telephony.UiccAccessRule;
-import android.telephony.UiccPortInfo;
-import android.telephony.UiccSlotInfo;
-import android.telephony.UiccSlotMapping;
-import android.telephony.euicc.EuiccManager;
-import android.text.TextUtils;
-import android.util.EventLog;
-import android.util.LocalLog;
-import android.util.Log;
-
-import com.android.ims.ImsManager;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.data.PhoneSwitcher;
-import com.android.internal.telephony.metrics.TelephonyMetrics;
-import com.android.internal.telephony.subscription.SubscriptionManagerService;
-import com.android.internal.telephony.uicc.IccUtils;
-import com.android.internal.telephony.uicc.UiccCard;
-import com.android.internal.telephony.uicc.UiccController;
-import com.android.internal.telephony.uicc.UiccProfile;
-import com.android.internal.telephony.uicc.UiccSlot;
-import com.android.internal.telephony.util.ArrayUtils;
-import com.android.internal.telephony.util.TelephonyUtils;
-import com.android.telephony.Rlog;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-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.Map.Entry;
-import java.util.Objects;
-import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.stream.Collectors;
-
-/**
- * Implementation of the ISub interface.
- *
- * Any setters which take subId, slotIndex or phoneId as a parameter will throw an exception if the
- * parameter equals the corresponding INVALID_XXX_ID or DEFAULT_XXX_ID.
- *
- * All getters will lookup the corresponding default if the parameter is DEFAULT_XXX_ID. Ie calling
- * getPhoneId(DEFAULT_SUB_ID) will return the same as getPhoneId(getDefaultSubId()).
- *
- * Finally, any getters which perform the mapping between subscriptions, slots and phones will
- * return the corresponding INVALID_XXX_ID if the parameter is INVALID_XXX_ID. All other getters
- * will fail and return the appropriate error value. Ie calling
- * getSlotIndex(INVALID_SUBSCRIPTION_ID) will return INVALID_SIM_SLOT_INDEX and calling
- * getSubInfoForSubscriber(INVALID_SUBSCRIPTION_ID) will return null.
- *
- */
-public class SubscriptionController extends ISub.Stub {
-    private static final String LOG_TAG = "SubscriptionController";
-    private static final boolean DBG = false;
-    private static final boolean VDBG = Rlog.isLoggable(LOG_TAG, Log.VERBOSE);
-    private static final boolean DBG_CACHE = false;
-    private static final int DEPRECATED_SETTING = -1;
-    private static final ParcelUuid INVALID_GROUP_UUID =
-            ParcelUuid.fromString(CarrierConfigManager.REMOVE_GROUP_UUID_STRING);
-    private final LocalLog mLocalLog = new LocalLog(128);
-    private static final int SUB_ID_FOUND = 1;
-    private static final int NO_ENTRY_FOR_SLOT_INDEX = -1;
-    private static final int SUB_ID_NOT_IN_SLOT = -2;
-
-    // Lock that both mCacheActiveSubInfoList and mCacheOpportunisticSubInfoList use.
-    private Object mSubInfoListLock = new Object();
-
-    /* The Cache of Active SubInfoRecord(s) list of currently in use SubInfoRecord(s) */
-    private final List<SubscriptionInfo> mCacheActiveSubInfoList = new ArrayList<>();
-
-    /* Similar to mCacheActiveSubInfoList but only caching opportunistic subscriptions. */
-    private List<SubscriptionInfo> mCacheOpportunisticSubInfoList = new ArrayList<>();
-    private AtomicBoolean mOpptSubInfoListChangedDirtyBit = new AtomicBoolean();
-
-    private static final Comparator<SubscriptionInfo> SUBSCRIPTION_INFO_COMPARATOR =
-            (arg0, arg1) -> {
-                // Primary sort key on SimSlotIndex
-                int flag = arg0.getSimSlotIndex() - arg1.getSimSlotIndex();
-                if (flag == 0) {
-                    // Secondary sort on SubscriptionId
-                    return arg0.getSubscriptionId() - arg1.getSubscriptionId();
-                }
-                return flag;
-            };
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    protected final Object mLock = new Object();
-
-    /** The singleton instance. */
-    protected static SubscriptionController sInstance = null;
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    protected Context mContext;
-    protected TelephonyManager mTelephonyManager;
-    protected UiccController mUiccController;
-
-    /**
-     * Apps targeting on Android T and beyond will get an empty list if there is no access to device
-     * identifiers nor has carrier privileges when calling
-     * SubscriptionManager#getSubscriptionsInGroup.
-     */
-    @ChangeId
-    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
-    public static final long REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID = 213902861L;
-
-    private AppOpsManager mAppOps;
-
-    // Each slot can have multiple subs.
-    private static class WatchedSlotIndexToSubIds {
-        private final Map<Integer, ArrayList<Integer>> mSlotIndexToSubIds =
-                new ConcurrentHashMap<>();
-
-        public void clear() {
-            mSlotIndexToSubIds.clear();
-            invalidateDefaultSubIdCaches();
-            invalidateSlotIndexCaches();
-        }
-
-        public Set<Entry<Integer, ArrayList<Integer>>> entrySet() {
-            return mSlotIndexToSubIds.entrySet();
-        }
-
-        // Force all updates to data structure through wrapper.
-        public ArrayList<Integer> getCopy(int slotIndex) {
-            ArrayList<Integer> subIdList = mSlotIndexToSubIds.get(slotIndex);
-            if (subIdList == null) {
-                return null;
-            }
-
-            return new ArrayList<>(subIdList);
-        }
-
-        public void put(int slotIndex, ArrayList<Integer> value) {
-            mSlotIndexToSubIds.put(slotIndex, value);
-            invalidateDefaultSubIdCaches();
-            invalidateSlotIndexCaches();
-        }
-
-        public void remove(int slotIndex) {
-            mSlotIndexToSubIds.remove(slotIndex);
-            invalidateDefaultSubIdCaches();
-            invalidateSlotIndexCaches();
-        }
-
-        public int size() {
-            return mSlotIndexToSubIds.size();
-        }
-
-        @VisibleForTesting
-        public Map<Integer, ArrayList<Integer>> getMap() {
-            return mSlotIndexToSubIds;
-        }
-
-        public int removeFromSubIdList(int slotIndex, int subId) {
-            ArrayList<Integer> subIdList = mSlotIndexToSubIds.get(slotIndex);
-            if (subIdList == null) {
-                return NO_ENTRY_FOR_SLOT_INDEX;
-            } else {
-                if (subIdList.contains(subId)) {
-                    subIdList.remove(new Integer(subId));
-                    if (subIdList.isEmpty()) {
-                        mSlotIndexToSubIds.remove(slotIndex);
-                    }
-                    invalidateDefaultSubIdCaches();
-                    invalidateSlotIndexCaches();
-                    return SUB_ID_FOUND;
-                } else {
-                    return SUB_ID_NOT_IN_SLOT;
-                }
-            }
-        }
-
-        public void addToSubIdList(int slotIndex, Integer value) {
-            ArrayList<Integer> subIdList = mSlotIndexToSubIds.get(slotIndex);
-            if (subIdList == null) {
-                subIdList = new ArrayList<Integer>();
-                subIdList.add(value);
-                mSlotIndexToSubIds.put(slotIndex, subIdList);
-            } else {
-                subIdList.add(value);
-            }
-            invalidateDefaultSubIdCaches();
-            invalidateSlotIndexCaches();
-        }
-
-        public void clearSubIdList(int slotIndex) {
-            ArrayList<Integer> subIdList = mSlotIndexToSubIds.get(slotIndex);
-            if (subIdList != null) {
-                subIdList.clear();
-                invalidateDefaultSubIdCaches();
-                invalidateSlotIndexCaches();
-            }
-        }
-    }
-
-    public static class WatchedInt {
-        private int mValue;
-
-        public WatchedInt(int initialValue) {
-            mValue = initialValue;
-        }
-
-        public int get() {
-            return mValue;
-        }
-
-        public void set(int newValue) {
-            mValue = newValue;
-        }
-    }
-
-    private final WatchedSlotIndexToSubIds mSlotIndexToSubIds = new WatchedSlotIndexToSubIds();
-
-    private final WatchedInt mDefaultFallbackSubId =
-            new WatchedInt(SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
-        @Override
-        public void set(int newValue) {
-            super.set(newValue);
-            invalidateDefaultSubIdCaches();
-            invalidateSlotIndexCaches();
-        }
-    };
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private static int mDefaultPhoneId = SubscriptionManager.DEFAULT_PHONE_INDEX;
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private int[] colorArr;
-    private long mLastISubServiceRegTime;
-    private RegistrantList mUiccAppsEnableChangeRegList = new RegistrantList();
-
-    // The properties that should be shared and synced across grouped subscriptions.
-    private static final Set<String> GROUP_SHARING_PROPERTIES = new HashSet<>(Arrays.asList(
-            SubscriptionManager.ENHANCED_4G_MODE_ENABLED,
-            SubscriptionManager.VT_IMS_ENABLED,
-            SubscriptionManager.WFC_IMS_ENABLED,
-            SubscriptionManager.WFC_IMS_MODE,
-            SubscriptionManager.WFC_IMS_ROAMING_MODE,
-            SubscriptionManager.WFC_IMS_ROAMING_ENABLED,
-            SubscriptionManager.DATA_ROAMING,
-            SubscriptionManager.DISPLAY_NAME,
-            SubscriptionManager.ENABLED_MOBILE_DATA_POLICIES,
-            SubscriptionManager.UICC_APPLICATIONS_ENABLED,
-            SubscriptionManager.IMS_RCS_UCE_ENABLED,
-            SubscriptionManager.CROSS_SIM_CALLING_ENABLED,
-            SubscriptionManager.NR_ADVANCED_CALLING_ENABLED,
-            SubscriptionManager.USER_HANDLE
-    ));
-
-    public static SubscriptionController init(Context c) {
-        synchronized (SubscriptionController.class) {
-            if (sInstance == null) {
-                sInstance = new SubscriptionController(c);
-            } else {
-                Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
-            }
-            return sInstance;
-        }
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static SubscriptionController getInstance() {
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            throw new RuntimeException("getInstance should not be called.");
-        }
-        if (sInstance == null) {
-            Log.wtf(LOG_TAG, "getInstance null");
-        }
-
-        return sInstance;
-    }
-
-    protected SubscriptionController(Context c) {
-        internalInit(c);
-        migrateImsSettings();
-    }
-
-    protected void internalInit(Context c) {
-        mContext = c;
-        mTelephonyManager = TelephonyManager.from(mContext);
-
-        try {
-            mUiccController = UiccController.getInstance();
-        } catch(RuntimeException ex) {
-            throw new RuntimeException(
-                    "UiccController has to be initialised before SubscriptionController init");
-        }
-
-        mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
-
-        ServiceRegisterer subscriptionServiceRegisterer = TelephonyFrameworkInitializer
-                .getTelephonyServiceManager()
-                .getSubscriptionServiceRegisterer();
-        if (subscriptionServiceRegisterer.get() == null) {
-            subscriptionServiceRegisterer.register(this);
-            mLastISubServiceRegTime = System.currentTimeMillis();
-        }
-
-        // clear SLOT_INDEX for all subs
-        clearSlotIndexForSubInfoRecords();
-
-        // Cache Setting values
-        cacheSettingValues();
-
-        // Initial invalidate activates caching.
-        invalidateDefaultSubIdCaches();
-        invalidateDefaultDataSubIdCaches();
-        invalidateDefaultSmsSubIdCaches();
-        invalidateActiveDataSubIdCaches();
-        invalidateSlotIndexCaches();
-
-        mContext.getContentResolver().registerContentObserver(
-                SubscriptionManager.SIM_INFO_SUW_RESTORE_CONTENT_URI, false,
-                new ContentObserver(new Handler()) {
-                    @Override
-                    public void onChange(boolean selfChange, Uri uri) {
-                        if (uri.equals(SubscriptionManager.SIM_INFO_SUW_RESTORE_CONTENT_URI)) {
-                            refreshCachedActiveSubscriptionInfoList();
-                            notifySubscriptionInfoChanged();
-
-                            SubscriptionManager subManager = SubscriptionManager.from(mContext);
-                            for (SubscriptionInfo subInfo : getActiveSubscriptionInfoList(
-                                    mContext.getOpPackageName(), mContext.getAttributionTag())) {
-                                if (SubscriptionController.getInstance()
-                                        .isActiveSubId(subInfo.getSubscriptionId())) {
-                                    ImsManager imsManager = ImsManager.getInstance(mContext,
-                                            subInfo.getSimSlotIndex());
-                                    imsManager.updateImsServiceConfig();
-                                }
-                            }
-                        }
-                    }
-                });
-
-        SubscriptionManager.invalidateSubscriptionManagerServiceEnabledCaches();
-
-        if (DBG) logdl("[SubscriptionController] init by Context");
-    }
-
-    /**
-     * Should only be triggered once.
-     */
-    public void notifySubInfoReady() {
-        // broadcast default subId.
-        sendDefaultChangedBroadcast(SubscriptionManager.getDefaultSubscriptionId());
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private boolean isSubInfoReady() {
-        return SubscriptionInfoUpdater.isSubInfoInitialized();
-    }
-
-    /**
-     * This function marks SIM_SLOT_INDEX as INVALID for all subscriptions in the database. This
-     * should be done as part of initialization.
-     *
-     * TODO: SIM_SLOT_INDEX is based on current state and should not even be persisted in the
-     * database.
-     */
-    private void clearSlotIndexForSubInfoRecords() {
-        if (mContext == null) {
-            logel("[clearSlotIndexForSubInfoRecords] TelephonyManager or mContext is null");
-            return;
-        }
-
-        // Update all subscriptions in simInfo db with invalid slot index
-        ContentValues value = new ContentValues(1);
-        value.put(SubscriptionManager.SIM_SLOT_INDEX, SubscriptionManager.INVALID_SIM_SLOT_INDEX);
-        mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, value, null, null);
-    }
-
-    /**
-     * Cache the Settings values by reading these values from Setting from disk to prevent disk I/O
-     * access during the API calling. This is based on an assumption that the Settings system will
-     * itself cache this value after the first read and thus only the first read after boot will
-     * access the disk.
-     */
-    private void cacheSettingValues() {
-        Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION,
-                        SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-
-        Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION,
-                        SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-
-        Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION,
-                        SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    protected void enforceModifyPhoneState(String message) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.MODIFY_PHONE_STATE, message);
-    }
-
-    private void enforceReadPrivilegedPhoneState(String message) {
-        mContext.enforceCallingOrSelfPermission(
-                Manifest.permission.READ_PRIVILEGED_PHONE_STATE, message);
-    }
-
-    private void enforceManageSubscriptionUserAssociation(String message) {
-        mContext.enforceCallingOrSelfPermission(
-                Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION, message);
-    }
-
-    /**
-     * Returns whether the {@code callingPackage} has access to subscriber identifiers on the
-     * specified {@code subId} using the provided {@code message} in any resulting
-     * SecurityException.
-     */
-    private boolean hasSubscriberIdentifierAccess(int subId, String callingPackage,
-            String callingFeatureId, String message, boolean reportFailure) {
-        try {
-            return TelephonyPermissions.checkCallingOrSelfReadSubscriberIdentifiers(mContext, subId,
-                    callingPackage, callingFeatureId, message, reportFailure);
-        } catch (SecurityException e) {
-            // A SecurityException indicates that the calling package is targeting at least the
-            // minimum level that enforces identifier access restrictions and the new access
-            // requirements are not met.
-            return false;
-        }
-    }
-
-    /**
-     * Returns whether the {@code callingPackage} has access to the phone number on the specified
-     * {@code subId} using the provided {@code message} in any resulting SecurityException.
-     */
-    private boolean hasPhoneNumberAccess(int subId, String callingPackage, String callingFeatureId,
-            String message) {
-        try {
-            return TelephonyPermissions.checkCallingOrSelfReadPhoneNumber(mContext, subId,
-                    callingPackage, callingFeatureId, message);
-        } catch (SecurityException e) {
-            return false;
-        }
-    }
-
-    /**
-     * Notify the changed of subscription info.
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public void notifySubscriptionInfoChanged() {
-        TelephonyRegistryManager trm =
-                (TelephonyRegistryManager)
-                        mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
-        if (DBG) logd("notifySubscriptionInfoChanged:");
-        trm.notifySubscriptionInfoChanged();
-
-        MultiSimSettingController.getInstance().notifySubscriptionInfoChanged();
-        TelephonyMetrics metrics = TelephonyMetrics.getInstance();
-        List<SubscriptionInfo> subInfos;
-        synchronized (mSubInfoListLock) {
-            subInfos = new ArrayList<>(mCacheActiveSubInfoList);
-        }
-
-        if (mOpptSubInfoListChangedDirtyBit.getAndSet(false)) {
-            notifyOpportunisticSubscriptionInfoChanged();
-        }
-        metrics.updateActiveSubscriptionInfoList(subInfos);
-    }
-
-    /**
-     * New SubInfoRecord instance and fill in detail info
-     * @param cursor The database cursor
-     * @return the query result of desired SubInfoRecord
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private SubscriptionInfo getSubInfoRecord(Cursor cursor) {
-        SubscriptionInfo.Builder builder = new SubscriptionInfo.Builder();
-        int id = cursor.getInt(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID));
-        builder.setId(id)
-                .setIccId(cursor.getString(cursor.getColumnIndexOrThrow(
-                        SubscriptionManager.ICC_ID)))
-                .setSimSlotIndex(cursor.getInt(cursor.getColumnIndexOrThrow(
-                        SubscriptionManager.SIM_SLOT_INDEX)))
-                .setDisplayName(cursor.getString(cursor.getColumnIndexOrThrow(
-                        SubscriptionManager.DISPLAY_NAME)))
-                .setCarrierName(cursor.getString(cursor.getColumnIndexOrThrow(
-                        SubscriptionManager.CARRIER_NAME)))
-                .setDisplayNameSource(cursor.getInt(cursor.getColumnIndexOrThrow(
-                        SubscriptionManager.NAME_SOURCE)))
-                .setIconTint(cursor.getInt(cursor.getColumnIndexOrThrow(
-                        SubscriptionManager.HUE)))
-                .setDataRoaming(cursor.getInt(cursor.getColumnIndexOrThrow(
-                        SubscriptionManager.DATA_ROAMING)))
-                .setMcc(cursor.getString(cursor.getColumnIndexOrThrow(
-                        SubscriptionManager.MCC_STRING)))
-                .setMnc(cursor.getString(cursor.getColumnIndexOrThrow(
-                        SubscriptionManager.MNC_STRING)));
-
-        String ehplmnsRaw = cursor.getString(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.EHPLMNS));
-        String hplmnsRaw = cursor.getString(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.HPLMNS));
-        String[] ehplmns = ehplmnsRaw == null ? null : ehplmnsRaw.split(",");
-        String[] hplmns = hplmnsRaw == null ? null : hplmnsRaw.split(",");
-
-        builder.setEhplmns(ehplmns).setHplmns(hplmns);
-
-
-        // CARD_ID is the private ICCID/EID string, also known as the card string
-        String cardString = cursor.getString(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.CARD_ID));
-        builder.setCardString(cardString);
-        // publicCardId is the publicly exposed int card ID
-        int publicCardId = mUiccController.convertToPublicCardId(cardString);
-        builder.setCardId(publicCardId);
-
-        builder.setCountryIso(cursor.getString(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.ISO_COUNTRY_CODE)))
-                .setCarrierId(cursor.getInt(cursor.getColumnIndexOrThrow(
-                        SubscriptionManager.CARRIER_ID)));
-
-        boolean isEmbedded = cursor.getInt(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.IS_EMBEDDED)) == 1;
-        builder.setEmbedded(isEmbedded);
-        if (isEmbedded) {
-            builder.setNativeAccessRules(UiccAccessRule.decodeRules(cursor.getBlob(
-                            cursor.getColumnIndexOrThrow(SubscriptionManager.ACCESS_RULES))));
-        }
-
-        builder.setCarrierConfigAccessRules(UiccAccessRule.decodeRules(cursor.getBlob(
-                cursor.getColumnIndexOrThrow(
-                        SubscriptionManager.ACCESS_RULES_FROM_CARRIER_CONFIGS))))
-                .setOpportunistic(cursor.getInt(cursor.getColumnIndexOrThrow(
-                        SubscriptionManager.IS_OPPORTUNISTIC)) == 1)
-                .setGroupUuid(cursor.getString(cursor.getColumnIndexOrThrow(
-                        SubscriptionManager.GROUP_UUID)))
-                .setProfileClass(cursor.getInt(cursor.getColumnIndexOrThrow(
-                        SubscriptionManager.PROFILE_CLASS)))
-                .setPortIndex(cursor.getInt(cursor.getColumnIndexOrThrow(
-                        SubscriptionManager.PORT_INDEX)))
-                .setType(cursor.getInt(cursor.getColumnIndexOrThrow(
-                        SubscriptionManager.SUBSCRIPTION_TYPE)))
-                .setGroupOwner(getOptionalStringFromCursor(cursor, SubscriptionManager.GROUP_OWNER,
-                        /*defaultVal*/ null))
-                .setUiccApplicationsEnabled(cursor.getInt(cursor.getColumnIndexOrThrow(
-                        SubscriptionManager.UICC_APPLICATIONS_ENABLED)) == 1)
-                .setUsageSetting(cursor.getInt(cursor.getColumnIndexOrThrow(
-                        SubscriptionManager.USAGE_SETTING)));
-
-        // If line1number has been set to a different number, use it instead.
-        String number = cursor.getString(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.NUMBER));
-        String line1Number = mTelephonyManager.getLine1Number(id);
-        if (!TextUtils.isEmpty(line1Number) && !line1Number.equals(number)) {
-            number = line1Number;
-        }
-        builder.setNumber(number);
-
-        // FIXME(b/210771052): constructing a complete SubscriptionInfo requires a port index,
-        // but the port index isn't available here. Should it actually be part of SubscriptionInfo?
-
-        return builder.build();
-    }
-
-    private String getOptionalStringFromCursor(Cursor cursor, String column, String defaultVal) {
-        // Return defaultVal if the column doesn't exist.
-        int columnIndex = cursor.getColumnIndex(column);
-        return (columnIndex == -1) ? defaultVal : cursor.getString(columnIndex);
-    }
-
-    /**
-     * Get a subscription that matches IccId.
-     * @return null if there isn't a match, or subscription info if there is one.
-     */
-    public SubscriptionInfo getSubInfoForIccId(String iccId) {
-        List<SubscriptionInfo> info = getSubInfo(
-                SubscriptionManager.ICC_ID + "=\'" + iccId + "\'", null);
-        if (info == null || info.size() == 0) return null;
-        // Should be at most one subscription with the iccid.
-        return info.get(0);
-    }
-
-    /**
-     * Query SubInfoRecord(s) from subinfo database
-     * @param selection A filter declaring which rows to return
-     * @param queryKey query key content
-     * @return Array list of queried result from database
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public List<SubscriptionInfo> getSubInfo(String selection, Object queryKey) {
-        if (VDBG) logd("selection:" + selection + ", querykey: " + queryKey);
-        String[] selectionArgs = null;
-        if (queryKey != null) {
-            selectionArgs = new String[] {queryKey.toString()};
-        }
-        ArrayList<SubscriptionInfo> subList = null;
-        Cursor cursor = mContext.getContentResolver().query(SubscriptionManager.CONTENT_URI,
-                null, selection, selectionArgs, null);
-        try {
-            if (cursor != null) {
-                while (cursor.moveToNext()) {
-                    SubscriptionInfo subInfo = getSubInfoRecord(cursor);
-                    if (subInfo != null) {
-                        if (subList == null) {
-                            subList = new ArrayList<SubscriptionInfo>();
-                        }
-                        subList.add(subInfo);
-                    }
-                }
-            } else {
-                if (DBG) logd("Query fail");
-            }
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
-        }
-
-        return subList;
-    }
-
-    /**
-     * Find unused color to be set for new SubInfoRecord
-     * @param callingPackage The package making the IPC.
-     * @param callingFeatureId The feature in the package
-     * @return RGB integer value of color
-     */
-    private int getUnusedColor(String callingPackage, String callingFeatureId) {
-        List<SubscriptionInfo> availableSubInfos = getActiveSubscriptionInfoList(callingPackage,
-                callingFeatureId);
-        colorArr = mContext.getResources().getIntArray(com.android.internal.R.array.sim_colors);
-        int colorIdx = 0;
-
-        if (availableSubInfos != null) {
-            for (int i = 0; i < colorArr.length; i++) {
-                int j;
-                for (j = 0; j < availableSubInfos.size(); j++) {
-                    if (colorArr[i] == availableSubInfos.get(j).getIconTint()) {
-                        break;
-                    }
-                }
-                if (j == availableSubInfos.size()) {
-                    return colorArr[i];
-                }
-            }
-            colorIdx = availableSubInfos.size() % colorArr.length;
-        }
-        return colorArr[colorIdx];
-    }
-
-    @Deprecated
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public SubscriptionInfo getActiveSubscriptionInfo(int subId, String callingPackage) {
-        return getActiveSubscriptionInfo(subId, callingPackage, null);
-    }
-
-    /**
-     * Get the active SubscriptionInfo with the subId key
-     * @param subId The unique SubscriptionInfo key in database
-     * @param callingPackage The package making the IPC.
-     * @param callingFeatureId The feature in the package
-     * @return SubscriptionInfo, maybe null if its not active
-     */
-    @Override
-    public SubscriptionInfo getActiveSubscriptionInfo(int subId, String callingPackage,
-            String callingFeatureId) {
-        if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subId, callingPackage,
-                callingFeatureId, "getActiveSubscriptionInfo")) {
-            return null;
-        }
-
-        // Now that all security checks passes, perform the operation as ourselves.
-        final long identity = Binder.clearCallingIdentity();
-        List<SubscriptionInfo> subList;
-        try {
-            subList = getActiveSubscriptionInfoList(
-                    mContext.getOpPackageName(), mContext.getAttributionTag());
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-        if (subList != null) {
-            for (SubscriptionInfo si : subList) {
-                if (si.getSubscriptionId() == subId) {
-                    if (VDBG) {
-                        logd("[getActiveSubscriptionInfo]+ subId=" + subId + " subInfo=" + si);
-                    }
-                    return conditionallyRemoveIdentifiers(si, callingPackage, callingFeatureId,
-                            "getActiveSubscriptionInfo");
-                }
-            }
-        }
-        if (DBG) {
-            logd("[getActiveSubscriptionInfo]- subId=" + subId
-                    + " subList=" + subList + " subInfo=null");
-        }
-
-        return null;
-    }
-
-    /**
-     * Get a single subscription info record for a given subscription.
-     *
-     * @param subId the subId to query.
-     *
-     * @hide
-     */
-    public SubscriptionInfo getSubscriptionInfo(int subId) {
-        synchronized (mSubInfoListLock) {
-            // check cache for active subscriptions first, before querying db
-            for (SubscriptionInfo subInfo : mCacheActiveSubInfoList) {
-                if (subInfo.getSubscriptionId() == subId) {
-                    return subInfo;
-                }
-            }
-
-            // check cache for opportunistic subscriptions too, before querying db
-            for (SubscriptionInfo subInfo : mCacheOpportunisticSubInfoList) {
-                if (subInfo.getSubscriptionId() == subId) {
-                    return subInfo;
-                }
-            }
-        }
-
-        List<SubscriptionInfo> subInfoList = getSubInfo(
-                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + subId, null);
-        if (subInfoList == null || subInfoList.isEmpty()) return null;
-        return subInfoList.get(0);
-    }
-
-    /**
-     * Get the active SubscriptionInfo associated with the iccId
-     * @param iccId the IccId of SIM card
-     * @param callingPackage The package making the IPC.
-     * @param callingFeatureId The feature in the package
-     * @return SubscriptionInfo, maybe null if its not active
-     */
-    @Override
-    public SubscriptionInfo getActiveSubscriptionInfoForIccId(String iccId, String callingPackage,
-            String callingFeatureId) {
-        enforceReadPrivilegedPhoneState("getActiveSubscriptionInfoForIccId");
-        return getActiveSubscriptionInfoForIccIdInternal(iccId);
-    }
-
-    /**
-     * Get the active SubscriptionInfo associated with the given iccId. The caller *must* perform
-     * permission checks when using this method.
-     */
-    private SubscriptionInfo getActiveSubscriptionInfoForIccIdInternal(String iccId) {
-        if (iccId == null) {
-            return null;
-        }
-
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            List<SubscriptionInfo> subList = getActiveSubscriptionInfoList(
-                    mContext.getOpPackageName(), mContext.getAttributionTag());
-            if (subList != null) {
-                for (SubscriptionInfo si : subList) {
-                    if (iccId.equals(si.getIccId())) {
-                        if (DBG)
-                            logd("[getActiveSubInfoUsingIccId]+ iccId=" + iccId + " subInfo=" + si);
-                        return si;
-                    }
-                }
-            }
-            if (DBG) {
-                logd("[getActiveSubInfoUsingIccId]+ iccId=" + iccId
-                        + " subList=" + subList + " subInfo=null");
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-
-        return null;
-    }
-
-    /**
-     * Get the active SubscriptionInfo associated with the slotIndex.
-     * This API does not return details on Remote-SIM subscriptions.
-     * @param slotIndex the slot which the subscription is inserted
-     * @param callingPackage The package making the IPC.
-     * @param callingFeatureId The feature in the package
-     * @return SubscriptionInfo, null for Remote-SIMs or non-active slotIndex.
-     */
-    @Override
-    public SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex,
-            String callingPackage, String callingFeatureId) {
-        Phone phone = PhoneFactory.getPhone(slotIndex);
-        if (phone == null) {
-            if (DBG) {
-                loge("[getActiveSubscriptionInfoForSimSlotIndex] no phone, slotIndex=" + slotIndex);
-            }
-            return null;
-        }
-        if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                mContext, phone.getSubId(), callingPackage, callingFeatureId,
-                "getActiveSubscriptionInfoForSimSlotIndex")) {
-            return null;
-        }
-
-        // Now that all security checks passes, perform the operation as ourselves.
-        final long identity = Binder.clearCallingIdentity();
-        List<SubscriptionInfo> subList;
-        try {
-            subList = getActiveSubscriptionInfoList(
-                    mContext.getOpPackageName(), mContext.getAttributionTag());
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-        if (subList != null) {
-            for (SubscriptionInfo si : subList) {
-                if (si.getSimSlotIndex() == slotIndex) {
-                    if (DBG) {
-                        logd("[getActiveSubscriptionInfoForSimSlotIndex]+ slotIndex="
-                                + slotIndex + " subId=" + si);
-                    } else {
-                        logd("[getActiveSubscriptionInfoForSimSlotIndex]+ slotIndex="
-                                + slotIndex + " subId=" + conditionallyRemoveIdentifiers(si, false,
-                                false));
-                    }
-                    return conditionallyRemoveIdentifiers(si, callingPackage, callingFeatureId,
-                            "getActiveSubscriptionInfoForSimSlotIndex");
-                }
-            }
-            if (DBG) {
-                logd("[getActiveSubscriptionInfoForSimSlotIndex]+ slotIndex=" + slotIndex
-                        + " subId=null");
-            }
-        } else {
-            if (DBG) {
-                logd("[getActiveSubscriptionInfoForSimSlotIndex]+ subList=null");
-            }
-        }
-
-
-        return null;
-    }
-
-    /**
-     * @param callingPackage The package making the IPC.
-     * @param callingFeatureId The feature in the package
-     * @return List of all SubscriptionInfo records in database,
-     * include those that were inserted before, maybe empty but not null.
-     * @hide
-     */
-    @Override
-    public List<SubscriptionInfo> getAllSubInfoList(String callingPackage,
-            String callingFeatureId) {
-        return getAllSubInfoList(callingPackage, callingFeatureId, false);
-    }
-
-    /**
-     * @param callingPackage The package making the IPC.
-     * @param callingFeatureId The feature in the package
-     * @param skipConditionallyRemoveIdentifier if set, skip removing identifier conditionally
-     * @return List of all SubscriptionInfo records in database,
-     * include those that were inserted before, maybe empty but not null.
-     * @hide
-     */
-    public List<SubscriptionInfo> getAllSubInfoList(String callingPackage,
-            String callingFeatureId, boolean skipConditionallyRemoveIdentifier) {
-        if (VDBG) logd("[getAllSubInfoList]+");
-
-        // This API isn't public, so no need to provide a valid subscription ID - we're not worried
-        // about carrier-privileged callers not having access.
-        if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                mContext, SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage,
-                callingFeatureId, "getAllSubInfoList")) {
-            return null;
-        }
-
-        // Now that all security checks passes, perform the operation as ourselves.
-        final long identity = Binder.clearCallingIdentity();
-        List<SubscriptionInfo> subList;
-        try {
-            subList = getSubInfo(null, null);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-        if (subList != null && !skipConditionallyRemoveIdentifier) {
-            if (VDBG) logd("[getAllSubInfoList]- " + subList.size() + " infos return");
-            subList = subList.stream().map(
-                    subscriptionInfo -> conditionallyRemoveIdentifiers(subscriptionInfo,
-                            callingPackage, callingFeatureId, "getAllSubInfoList"))
-                    .collect(Collectors.toList());
-        } else {
-            if (VDBG) logd("[getAllSubInfoList]- no info return");
-        }
-        return subList;
-    }
-
-    private List<SubscriptionInfo> makeCacheListCopyWithLock(List<SubscriptionInfo> cacheSubList) {
-        synchronized (mSubInfoListLock) {
-            return new ArrayList<>(cacheSubList);
-        }
-    }
-
-    @Deprecated
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public List<SubscriptionInfo> getActiveSubscriptionInfoList(String callingPackage) {
-        return getSubscriptionInfoListFromCacheHelper(callingPackage, null,
-                makeCacheListCopyWithLock(mCacheActiveSubInfoList));
-    }
-
-    /**
-     * Get the SubInfoRecord(s) of the currently active SIM(s) - which include both local
-     * and remote SIMs.
-     * @param callingPackage The package making the IPC.
-     * @param callingFeatureId The feature in the package
-     * @return Array list of currently inserted SubInfoRecord(s)
-     */
-    @Override
-    public List<SubscriptionInfo> getActiveSubscriptionInfoList(String callingPackage,
-            String callingFeatureId) {
-        return getSubscriptionInfoListFromCacheHelper(callingPackage, callingFeatureId,
-                makeCacheListCopyWithLock(mCacheActiveSubInfoList));
-    }
-
-    /**
-     * Refresh the cache of SubInfoRecord(s) of the currently available SIM(s) - including
-     * local & remote SIMs.
-     */
-    @VisibleForTesting  // For mockito to mock this method
-    public void refreshCachedActiveSubscriptionInfoList() {
-        boolean opptSubListChanged;
-
-        List<SubscriptionInfo> activeSubscriptionInfoList = getSubInfo(
-                SubscriptionManager.SIM_SLOT_INDEX + ">=0 OR "
-                + SubscriptionManager.SUBSCRIPTION_TYPE + "="
-                + SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM,
-                null);
-
-        synchronized (mSubInfoListLock) {
-            if (activeSubscriptionInfoList != null) {
-                // Log when active sub info changes.
-                if (mCacheActiveSubInfoList.size() != activeSubscriptionInfoList.size()
-                        || !mCacheActiveSubInfoList.containsAll(activeSubscriptionInfoList)) {
-                    logdl("Active subscription info list changed. " + activeSubscriptionInfoList);
-                }
-
-                mCacheActiveSubInfoList.clear();
-                activeSubscriptionInfoList.sort(SUBSCRIPTION_INFO_COMPARATOR);
-                mCacheActiveSubInfoList.addAll(activeSubscriptionInfoList);
-            } else {
-                logd("activeSubscriptionInfoList is null.");
-                mCacheActiveSubInfoList.clear();
-            }
-            if (DBG_CACHE) {
-                if (!mCacheActiveSubInfoList.isEmpty()) {
-                    for (SubscriptionInfo si : mCacheActiveSubInfoList) {
-                        logd("[refreshCachedActiveSubscriptionInfoList] Setting Cached info="
-                                + si);
-                    }
-                } else {
-                    logdl("[refreshCachedActiveSubscriptionInfoList]- no info return");
-                }
-            }
-        }
-
-        // Refresh cached opportunistic sub list and detect whether it's changed.
-        refreshCachedOpportunisticSubscriptionInfoList();
-    }
-
-    @Deprecated
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public int getActiveSubInfoCount(String callingPackage) {
-        return getActiveSubInfoCount(callingPackage, null);
-    }
-
-    /**
-     * Get the SUB count of active SUB(s)
-     * @param callingPackage The package making the IPC.
-     * @param callingFeatureId The feature in the package.
-     * @return active SIM count
-     */
-    @Override
-    public int getActiveSubInfoCount(String callingPackage, String callingFeatureId) {
-        // Let getActiveSubscriptionInfoList perform permission checks / filtering.
-        List<SubscriptionInfo> records = getActiveSubscriptionInfoList(callingPackage,
-                callingFeatureId);
-        if (records == null) {
-            if (VDBG) logd("[getActiveSubInfoCount] records null");
-            return 0;
-        }
-        if (VDBG) logd("[getActiveSubInfoCount]- count: " + records.size());
-        return records.size();
-    }
-
-    /**
-     * Get the SUB count of all SUB(s) in SubscriptoinInfo database
-     * @param callingPackage The package making the IPC.
-     * @param callingFeatureId The feature in the package
-     * @return all SIM count in database, include what was inserted before
-     */
-    public int getAllSubInfoCount(String callingPackage, String callingFeatureId) {
-        if (DBG) logd("[getAllSubInfoCount]+");
-
-        // This API isn't public, so no need to provide a valid subscription ID - we're not worried
-        // about carrier-privileged callers not having access.
-        if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
-                mContext, SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage,
-                callingFeatureId, "getAllSubInfoCount")) {
-            return 0;
-        }
-
-        // Now that all security checks passes, perform the operation as ourselves.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            Cursor cursor = mContext.getContentResolver().query(SubscriptionManager.CONTENT_URI,
-                    null, null, null, null);
-            try {
-                if (cursor != null) {
-                    int count = cursor.getCount();
-                    if (DBG) logd("[getAllSubInfoCount]- " + count + " SUB(s) in DB");
-                    return count;
-                }
-            } finally {
-                if (cursor != null) {
-                    cursor.close();
-                }
-            }
-            if (DBG) logd("[getAllSubInfoCount]- no SUB in DB");
-
-            return 0;
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    /**
-     * @return the maximum number of local subscriptions this device will support at any one time.
-     */
-    @Override
-    public int getActiveSubInfoCountMax() {
-        // FIXME: This valid now but change to use TelephonyDevController in the future
-        return mTelephonyManager.getSimCount();
-    }
-
-    @Override
-    public List<SubscriptionInfo> getAvailableSubscriptionInfoList(String callingPackage,
-            String callingFeatureId) {
-        try {
-            enforceReadPrivilegedPhoneState("getAvailableSubscriptionInfoList");
-        } catch (SecurityException e) {
-            try {
-                mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE, null);
-                // If caller doesn't have READ_PRIVILEGED_PHONE_STATE permission but only
-                // has READ_PHONE_STATE permission, log this event.
-                EventLog.writeEvent(0x534e4554, "185235454", Binder.getCallingUid());
-            } catch (SecurityException ex) {
-                // Ignore
-            }
-            throw new SecurityException("Need READ_PRIVILEGED_PHONE_STATE to call "
-                    + " getAvailableSubscriptionInfoList");
-        }
-
-        // Now that all security checks pass, perform the operation as ourselves.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            String selection = SubscriptionManager.SIM_SLOT_INDEX + ">=0 OR "
-                    + SubscriptionManager.SUBSCRIPTION_TYPE + "="
-                    + SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM;
-
-            EuiccManager euiccManager =
-                    (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE);
-            if (euiccManager.isEnabled()) {
-                selection += " OR " + SubscriptionManager.IS_EMBEDDED + "=1";
-            }
-
-            // Available eSIM profiles are reported by EuiccManager. However for physical SIMs if
-            // they are in inactive slot or programmatically disabled, they are still considered
-            // available. In this case we get their iccid from slot info and include their
-            // subscriptionInfos.
-            List<String> iccIds = getIccIdsOfInsertedPhysicalSims();
-
-            if (!iccIds.isEmpty()) {
-                selection += " OR ("  + getSelectionForIccIdList(iccIds.toArray(new String[0]))
-                        + ")";
-            }
-
-            List<SubscriptionInfo> subList = getSubInfo(selection, null /* queryKey */);
-
-            if (subList != null) {
-                subList.sort(SUBSCRIPTION_INFO_COMPARATOR);
-
-                if (VDBG) logdl("[getAvailableSubInfoList]- " + subList.size() + " infos return");
-            } else {
-                if (DBG) logdl("[getAvailableSubInfoList]- no info return");
-            }
-
-            return subList;
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    private List<String> getIccIdsOfInsertedPhysicalSims() {
-        List<String> ret = new ArrayList<>();
-        UiccSlot[] uiccSlots = UiccController.getInstance().getUiccSlots();
-        if (uiccSlots == null) return ret;
-
-        for (UiccSlot uiccSlot : uiccSlots) {
-            if (uiccSlot != null && uiccSlot.getCardState() != null
-                    && uiccSlot.getCardState().isCardPresent()
-                    && !uiccSlot.isEuicc()) {
-                // Non euicc slots will have single port, so use default port index.
-                String iccId = uiccSlot.getIccId(TelephonyManager.DEFAULT_PORT_INDEX);
-                if (!TextUtils.isEmpty(iccId)) {
-                    ret.add(IccUtils.stripTrailingFs(iccId));
-                }
-            }
-        }
-
-        return ret;
-    }
-
-    @Override
-    public List<SubscriptionInfo> getAccessibleSubscriptionInfoList(String callingPackage) {
-        EuiccManager euiccManager = (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE);
-        if (!euiccManager.isEnabled()) {
-            if (DBG) {
-                logdl("[getAccessibleSubInfoList] Embedded subscriptions are disabled");
-            }
-            return null;
-        }
-
-        // Verify that the given package belongs to the calling UID.
-        mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
-
-        // Perform the operation as ourselves. If the caller cannot read phone state, they may still
-        // have carrier privileges per the subscription metadata, so we always need to make the
-        // query and then filter the results.
-        final long identity = Binder.clearCallingIdentity();
-        List<SubscriptionInfo> subList;
-        try {
-            subList = getSubInfo(SubscriptionManager.IS_EMBEDDED + "=1", null);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-
-        if (subList == null) {
-            if (DBG) logdl("[getAccessibleSubInfoList] No info returned");
-            return null;
-        }
-
-        // Filter the list to only include subscriptions which the (restored) caller can manage.
-        List<SubscriptionInfo> filteredList = subList.stream()
-                .filter(subscriptionInfo ->
-                        subscriptionInfo.canManageSubscription(mContext, callingPackage))
-                .sorted(SUBSCRIPTION_INFO_COMPARATOR)
-                .collect(Collectors.toList());
-        if (VDBG) {
-            logdl("[getAccessibleSubInfoList] " + filteredList.size() + " infos returned");
-        }
-        return filteredList;
-    }
-
-    /**
-     * Return the list of subscriptions in the database which are either:
-     * <ul>
-     * <li>Embedded (but see note about {@code includeNonRemovableSubscriptions}, or
-     * <li>In the given list of current embedded ICCIDs (which may not yet be in the database, or
-     *     which may not currently be marked as embedded).
-     * </ul>
-     *
-     * <p>NOTE: This is not accessible to external processes, so it does not need a permission
-     * check. It is only intended for use by {@link SubscriptionInfoUpdater}.
-     *
-     * @param embeddedIccids all ICCIDs of available embedded subscriptions. This is used to surface
-     *     entries for profiles which had been previously deleted.
-     * @param isEuiccRemovable whether the current ICCID is removable. Non-removable subscriptions
-     *     will only be returned if the current ICCID is not removable; otherwise, they are left
-     *     alone (not returned here unless in the embeddedIccids list) under the assumption that
-     *     they will still be accessible when the eUICC containing them is activated.
-     */
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    public List<SubscriptionInfo> getSubscriptionInfoListForEmbeddedSubscriptionUpdate(
-            String[] embeddedIccids, boolean isEuiccRemovable) {
-        StringBuilder whereClause = new StringBuilder();
-        whereClause.append("(").append(SubscriptionManager.IS_EMBEDDED).append("=1");
-        if (isEuiccRemovable) {
-            // Current eUICC is removable, so don't return non-removable subscriptions (which would
-            // be deleted), as these are expected to still be present on a different, non-removable
-            // eUICC.
-            whereClause.append(" AND ").append(SubscriptionManager.IS_REMOVABLE).append("=1");
-        }
-        // Else, return both removable and non-removable subscriptions. This is expected to delete
-        // all removable subscriptions, which is desired as they may not be accessible.
-
-        whereClause.append(") OR ").append(SubscriptionManager.ICC_ID).append(" IN (");
-        // ICCIDs are validated to contain only numbers when passed in, and come from a trusted
-        // app, so no need to escape.
-        for (int i = 0; i < embeddedIccids.length; i++) {
-            if (i > 0) {
-                whereClause.append(",");
-            }
-            whereClause.append("'").append(embeddedIccids[i]).append("'");
-        }
-        whereClause.append(")");
-
-        List<SubscriptionInfo> list = getSubInfo(whereClause.toString(), null);
-        if (list == null) {
-            return Collections.emptyList();
-        }
-        return list;
-    }
-
-    @Override
-    public void requestEmbeddedSubscriptionInfoListRefresh(int cardId) {
-        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS,
-                "requestEmbeddedSubscriptionInfoListRefresh");
-        long token = Binder.clearCallingIdentity();
-        try {
-            PhoneFactory.requestEmbeddedSubscriptionInfoListRefresh(cardId, null /* callback */);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    /**
-     * Asynchronously refresh the embedded subscription info list for the embedded card has the
-     * given card id {@code cardId}.
-     *
-     * @param callback Optional callback to execute after the refresh completes. Must terminate
-     *     quickly as it will be called from SubscriptionInfoUpdater's handler thread.
-     */
-    // No permission check needed as this is not exposed via AIDL.
-    public void requestEmbeddedSubscriptionInfoListRefresh(
-            int cardId, @Nullable Runnable callback) {
-        PhoneFactory.requestEmbeddedSubscriptionInfoListRefresh(cardId, callback);
-    }
-
-    /**
-     * Asynchronously refresh the embedded subscription info list for the embedded card has the
-     * default card id return by {@link TelephonyManager#getCardIdForDefaultEuicc()}.
-     *
-     * @param callback Optional callback to execute after the refresh completes. Must terminate
-     *     quickly as it will be called from SubscriptionInfoUpdater's handler thread.
-     */
-    // No permission check needed as this is not exposed via AIDL.
-    public void requestEmbeddedSubscriptionInfoListRefresh(@Nullable Runnable callback) {
-        PhoneFactory.requestEmbeddedSubscriptionInfoListRefresh(
-                mTelephonyManager.getCardIdForDefaultEuicc(), callback);
-    }
-
-    /**
-     * Add a new subscription info record, if needed.
-     * @param uniqueId This is the unique identifier for the subscription within the specific
-     *                 subscription type.
-     * @param displayName human-readable name of the device the subscription corresponds to.
-     * @param slotIndex value for {@link SubscriptionManager#SIM_SLOT_INDEX}
-     * @param subscriptionType the type of subscription to be added.
-     * @return 0 if success, < 0 on error.
-     */
-    @Override
-    public int addSubInfo(String uniqueId, String displayName, int slotIndex,
-            int subscriptionType) {
-        if (DBG) {
-            String iccIdStr = uniqueId;
-            if (!isSubscriptionForRemoteSim(subscriptionType)) {
-                iccIdStr = SubscriptionInfo.givePrintableIccid(uniqueId);
-            }
-            logdl("[addSubInfoRecord]+ iccid: " + iccIdStr
-                    + ", slotIndex: " + slotIndex
-                    + ", subscriptionType: " + subscriptionType);
-        }
-
-        enforceModifyPhoneState("addSubInfo");
-
-        // Now that all security checks passes, perform the operation as ourselves.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            if (uniqueId == null) {
-                if (DBG) logdl("[addSubInfo]- null iccId");
-                return -1;
-            }
-
-            ContentResolver resolver = mContext.getContentResolver();
-            String selection = SubscriptionManager.ICC_ID + "=?";
-            String[] args;
-            if (isSubscriptionForRemoteSim(subscriptionType)) {
-                PackageManager packageManager = mContext.getPackageManager();
-                if (!packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
-                    logel("[addSubInfo] Remote SIM can only be added when FEATURE_AUTOMOTIVE"
-                            + " is supported");
-                    return -1;
-                }
-                selection += " AND " + SubscriptionManager.SUBSCRIPTION_TYPE + "=?";
-                args = new String[]{uniqueId, Integer.toString(subscriptionType)};
-            } else {
-                selection += " OR " + SubscriptionManager.ICC_ID + "=?";
-                args = new String[]{uniqueId, IccUtils.getDecimalSubstring(uniqueId)};
-            }
-            Cursor cursor = resolver.query(SubscriptionManager.CONTENT_URI,
-                    new String[]{SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID,
-                            SubscriptionManager.SIM_SLOT_INDEX, SubscriptionManager.NAME_SOURCE,
-                            SubscriptionManager.ICC_ID, SubscriptionManager.CARD_ID,
-                            SubscriptionManager.PORT_INDEX},
-                    selection, args, null);
-
-            boolean setDisplayName = false;
-            try {
-                boolean recordsDoNotExist = (cursor == null || !cursor.moveToFirst());
-                if (isSubscriptionForRemoteSim(subscriptionType)) {
-                    if (recordsDoNotExist) {
-                        // create a Subscription record
-                        slotIndex = SubscriptionManager.SLOT_INDEX_FOR_REMOTE_SIM_SUB;
-                        Uri uri = insertEmptySubInfoRecord(uniqueId, displayName,
-                                slotIndex, subscriptionType);
-                        if (DBG) logd("[addSubInfoRecord] New record created: " + uri);
-                    } else {
-                        if (DBG) logdl("[addSubInfoRecord] Record already exists");
-                    }
-                } else {  // Handle Local SIM devices
-                    if (recordsDoNotExist) {
-                        setDisplayName = true;
-                        Uri uri = insertEmptySubInfoRecord(uniqueId, slotIndex);
-                        if (DBG) logdl("[addSubInfoRecord] New record created: " + uri);
-                    } else { // there are matching records in the database for the given ICC_ID
-                        int subId = cursor.getInt(0);
-                        int oldSimInfoId = cursor.getInt(1);
-                        int nameSource = cursor.getInt(2);
-                        String oldIccId = cursor.getString(3);
-                        String oldCardId = cursor.getString(4);
-                        int oldPortIndex = cursor.getInt(5);
-                        ContentValues value = new ContentValues();
-
-                        if (slotIndex != oldSimInfoId) {
-                            value.put(SubscriptionManager.SIM_SLOT_INDEX, slotIndex);
-                        }
-
-                        if (oldIccId != null && oldIccId.length() < uniqueId.length()
-                                && (oldIccId.equals(IccUtils.getDecimalSubstring(uniqueId)))) {
-                            value.put(SubscriptionManager.ICC_ID, uniqueId);
-                        }
-
-                        UiccCard card = mUiccController.getUiccCardForPhone(slotIndex);
-                        if (card != null) {
-                            String cardId = card.getCardId();
-                            if (cardId != null && cardId != oldCardId) {
-                                value.put(SubscriptionManager.CARD_ID, cardId);
-                            }
-                        }
-
-                        //update portIndex for pSim
-                        UiccSlot slot = mUiccController.getUiccSlotForPhone(slotIndex);
-                        if (slot != null && !slot.isEuicc()) {
-                            int portIndex = slot.getPortIndexFromIccId(uniqueId);
-                            if (portIndex != oldPortIndex) {
-                                value.put(SubscriptionManager.PORT_INDEX, portIndex);
-                            }
-                        }
-
-                        if (value.size() > 0) {
-                            resolver.update(SubscriptionManager.getUriForSubscriptionId(subId),
-                                    value, null, null);
-                        }
-
-                        if (DBG) logdl("[addSubInfoRecord] Record already exists");
-                    }
-                }
-            } finally {
-                if (cursor != null) {
-                    cursor.close();
-                }
-            }
-
-            selection = SubscriptionManager.SIM_SLOT_INDEX + "=?";
-            args = new String[] {String.valueOf(slotIndex)};
-            if (isSubscriptionForRemoteSim(subscriptionType)) {
-                selection = SubscriptionManager.ICC_ID + "=? AND "
-                        + SubscriptionManager.SUBSCRIPTION_TYPE + "=?";
-                args = new String[]{uniqueId, Integer.toString(subscriptionType)};
-            }
-            cursor = resolver.query(SubscriptionManager.CONTENT_URI, null,
-                    selection, args, null);
-            try {
-                if (cursor != null && cursor.moveToFirst()) {
-                    do {
-                        int subId = cursor.getInt(cursor.getColumnIndexOrThrow(
-                                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID));
-                        // If sSlotIndexToSubIds already has the same subId for a slotIndex/phoneId,
-                        // do not add it.
-                        if (addToSubIdList(slotIndex, subId, subscriptionType)) {
-                            // TODO While two subs active, if user deactivats first
-                            // one, need to update the default subId with second one.
-
-                            // FIXME: Currently we assume phoneId == slotIndex which in the future
-                            // may not be true, for instance with multiple subs per slot.
-                            // But is true at the moment.
-                            int subIdCountMax = getActiveSubInfoCountMax();
-                            int defaultSubId = getDefaultSubId();
-                            if (DBG) {
-                                logdl("[addSubInfoRecord]"
-                                        + " mSlotIndexToSubIds.size=" + mSlotIndexToSubIds.size()
-                                        + " slotIndex=" + slotIndex + " subId=" + subId
-                                        + " defaultSubId=" + defaultSubId
-                                        + " simCount=" + subIdCountMax);
-                            }
-
-                            // Set the default sub if not set or if single sim device
-                            if (!isSubscriptionForRemoteSim(subscriptionType)) {
-                                if (!SubscriptionManager.isValidSubscriptionId(defaultSubId)
-                                        || subIdCountMax == 1
-                                        || mDefaultFallbackSubId.get() ==
-                                        SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
-                                    logdl("setting default fallback subid to " + subId);
-                                    setDefaultFallbackSubId(subId, subscriptionType);
-                                }
-                                // If single sim device, set this subscription as the default for
-                                // everything
-                                if (subIdCountMax == 1) {
-                                    if (DBG) {
-                                        logdl("[addSubInfoRecord] one sim set defaults to subId="
-                                                + subId);
-                                    }
-                                    setDefaultDataSubId(subId);
-                                    setDefaultSmsSubId(subId);
-                                    setDefaultVoiceSubId(subId);
-                                }
-                            } else {
-                                updateDefaultSubIdsIfNeeded(subId, subscriptionType);
-                            }
-                        } else {
-                            if (DBG) {
-                                logdl("[addSubInfoRecord] current SubId is already known, "
-                                        + "IGNORE");
-                            }
-                        }
-                        if (DBG) {
-                            logdl("[addSubInfoRecord] hashmap(" + slotIndex + "," + subId + ")");
-                        }
-                    } while (cursor.moveToNext());
-                }
-            } finally {
-                if (cursor != null) {
-                    cursor.close();
-                }
-            }
-
-            // Refresh the Cache of Active Subscription Info List. This should be done after
-            // updating sSlotIndexToSubIds which is done through addToSubIdList() above.
-            refreshCachedActiveSubscriptionInfoList();
-
-            if (isSubscriptionForRemoteSim(subscriptionType)) {
-                notifySubscriptionInfoChanged();
-            } else {  // Handle Local SIM devices
-                // Set Display name after sub id is set above so as to get valid simCarrierName
-                int subId = getSubId(slotIndex);
-                if (!SubscriptionManager.isValidSubscriptionId(subId)) {
-                    if (DBG) {
-                        logdl("[addSubInfoRecord]- getSubId failed invalid subId = " + subId);
-                    }
-                    return -1;
-                }
-                if (setDisplayName) {
-                    String simCarrierName = mTelephonyManager.getSimOperatorName(subId);
-                    String nameToSet;
-
-                    if (!TextUtils.isEmpty(simCarrierName)) {
-                        nameToSet = simCarrierName;
-                    } else {
-                        nameToSet = "CARD " + Integer.toString(slotIndex + 1);
-                    }
-
-                    ContentValues value = new ContentValues();
-                    value.put(SubscriptionManager.DISPLAY_NAME, nameToSet);
-                    resolver.update(SubscriptionManager.getUriForSubscriptionId(subId), value,
-                            null, null);
-
-                    // Refresh the Cache of Active Subscription Info List
-                    refreshCachedActiveSubscriptionInfoList();
-
-                    if (DBG) logdl("[addSubInfoRecord] sim name = " + nameToSet);
-                }
-
-                if (DBG) logdl("[addSubInfoRecord]- info size=" + mSlotIndexToSubIds.size());
-            }
-
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-        return 0;
-    }
-
-    private void updateDefaultSubIdsIfNeeded(int newDefault, int subscriptionType) {
-        if (DBG) {
-            logdl("[updateDefaultSubIdsIfNeeded] newDefault=" + newDefault
-                    + ", subscriptionType=" + subscriptionType);
-        }
-        // Set the default ot new value only if the current default is invalid.
-        if (!isActiveSubscriptionId(getDefaultSubId())) {
-            // current default is not valid anylonger. set a new default
-            if (DBG) {
-                logdl("[updateDefaultSubIdsIfNeeded] set sDefaultFallbackSubId=" + newDefault);
-            }
-            setDefaultFallbackSubId(newDefault, subscriptionType);
-        }
-
-        int value = getDefaultSmsSubId();
-        if (!isActiveSubscriptionId(value)) {
-            // current default is not valid. set it to the given newDefault value
-            setDefaultSmsSubId(newDefault);
-        }
-        value = getDefaultDataSubId();
-        if (!isActiveSubscriptionId(value)) {
-            setDefaultDataSubId(newDefault);
-        }
-        value = getDefaultVoiceSubId();
-        if (!isActiveSubscriptionId(value)) {
-            setDefaultVoiceSubId(newDefault);
-        }
-    }
-
-    /**
-     * This method returns true if the given subId is among the list of currently active
-     * subscriptions.
-     */
-    private boolean isActiveSubscriptionId(int subId) {
-        if (!SubscriptionManager.isValidSubscriptionId(subId)) return false;
-        ArrayList<Integer> subIdList = getActiveSubIdArrayList();
-        if (subIdList.isEmpty()) return false;
-        return subIdList.contains(new Integer(subId));
-    }
-
-    /*
-     * Delete subscription info record for the given device.
-     * @param uniqueId This is the unique identifier for the subscription within the specific
-     *                 subscription type.
-     * @param subscriptionType the type of subscription to be removed
-     * @return 0 if success, < 0 on error.
-     */
-    @Override
-    public int removeSubInfo(String uniqueId, int subscriptionType) {
-        enforceModifyPhoneState("removeSubInfo");
-        if (DBG) {
-            logd("[removeSubInfo] uniqueId: " + uniqueId
-                    + ", subscriptionType: " + subscriptionType);
-        }
-
-        // validate the given info - does it exist in the active subscription list
-        int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-        int slotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
-        synchronized (mSubInfoListLock) {
-            for (SubscriptionInfo info : mCacheActiveSubInfoList) {
-                if ((info.getSubscriptionType() == subscriptionType)
-                        && info.getIccId().equalsIgnoreCase(uniqueId)) {
-                    subId = info.getSubscriptionId();
-                    slotIndex = info.getSimSlotIndex();
-                    break;
-                }
-            }
-        }
-        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
-            if (DBG) {
-                logd("Invalid subscription details: subscriptionType = " + subscriptionType
-                        + ", uniqueId = " + uniqueId);
-            }
-            return -1;
-        }
-
-        if (DBG) logd("removing the subid : " + subId);
-
-        // Now that all security checks passes, perform the operation as ourselves.
-        int result = 0;
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            ContentResolver resolver = mContext.getContentResolver();
-            result = resolver.delete(SubscriptionManager.CONTENT_URI,
-                    SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=? AND "
-                            + SubscriptionManager.SUBSCRIPTION_TYPE + "=?",
-                    new String[]{Integer.toString(subId), Integer.toString(subscriptionType)});
-            if (result != 1) {
-                if (DBG) {
-                    logd("found NO subscription to remove with subscriptionType = "
-                            + subscriptionType + ", uniqueId = " + uniqueId);
-                }
-                return -1;
-            }
-            refreshCachedActiveSubscriptionInfoList();
-            result = mSlotIndexToSubIds.removeFromSubIdList(slotIndex, subId);
-            if (result == NO_ENTRY_FOR_SLOT_INDEX) {
-                loge("sSlotIndexToSubIds has no entry for slotIndex = " + slotIndex);
-            } else if (result == SUB_ID_NOT_IN_SLOT) {
-                loge("sSlotIndexToSubIds has no subid: " + subId + ", in index: " + slotIndex);
-            }
-
-            // Since a subscription is removed, if this one is set as default for any setting,
-            // set some other subid as the default.
-            int newDefault = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-            SubscriptionInfo info = null;
-            final List<SubscriptionInfo> records = getActiveSubscriptionInfoList(
-                    mContext.getOpPackageName(), mContext.getAttributionTag());
-            if (!records.isEmpty()) {
-                // yes, we have more subscriptions. pick the first one.
-                // FIXME do we need a policy to figure out which one is to be next default
-                info = records.get(0);
-            }
-            updateDefaultSubIdsIfNeeded(info.getSubscriptionId(), info.getSubscriptionType());
-
-            notifySubscriptionInfoChanged();
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-        return result;
-    }
-
-    /**
-     * Clear an subscriptionInfo to subinfo database if needed by updating slot index to invalid.
-     * @param slotIndex the slot which the SIM is removed
-     */
-    public void clearSubInfoRecord(int slotIndex) {
-        if (DBG) logdl("[clearSubInfoRecord]+ iccId:" + " slotIndex:" + slotIndex);
-
-        // update simInfo db with invalid slot index
-        ContentResolver resolver = mContext.getContentResolver();
-        ContentValues value = new ContentValues(1);
-        value.put(SubscriptionManager.SIM_SLOT_INDEX, SubscriptionManager.INVALID_SIM_SLOT_INDEX);
-        String where = "(" + SubscriptionManager.SIM_SLOT_INDEX + "=" + slotIndex + ")";
-        resolver.update(SubscriptionManager.CONTENT_URI, value, where, null);
-
-        // Refresh the Cache of Active Subscription Info List
-        refreshCachedActiveSubscriptionInfoList();
-
-        boolean isFallBackRefreshRequired = false;
-        if (mDefaultFallbackSubId.get() > SubscriptionManager.INVALID_SUBSCRIPTION_ID &&
-                mSlotIndexToSubIds.getCopy(slotIndex) != null &&
-                mSlotIndexToSubIds.getCopy(slotIndex).contains(mDefaultFallbackSubId.get())) {
-            isFallBackRefreshRequired = true;
-        }
-        mSlotIndexToSubIds.remove(slotIndex);
-        // set mDefaultFallbackSubId to invalid in case mSlotIndexToSubIds do not have any entries
-        if (mSlotIndexToSubIds.size() ==0 ) {
-            mDefaultFallbackSubId.set(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-        } else if (isFallBackRefreshRequired) {
-            // set mDefaultFallbackSubId to valid subId from mSlotIndexToSubIds
-            for (int index = 0; index < getActiveSubIdArrayList().size(); index ++) {
-                int subId = getActiveSubIdArrayList().get(index);
-                if (subId > SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
-                    mDefaultFallbackSubId.set(subId);
-                    break;
-                }
-            }
-        }
-    }
-
-    /**
-     * Insert an empty SubInfo record into the database.
-     *
-     * <p>NOTE: This is not accessible to external processes, so it does not need a permission
-     * check. It is only intended for use by {@link SubscriptionInfoUpdater}. If there is a
-     * subscription record exist with the same ICCID, no new empty record will be created.
-     *
-     * @return the URL of the newly created row. Return <code>null</code> if no new empty record is
-     * created.
-     */
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    @Nullable
-    public Uri insertEmptySubInfoRecord(String iccId, int slotIndex) {
-        if (getSubInfoForIccId(iccId) != null) {
-            loge("insertEmptySubInfoRecord: Found existing record by ICCID. Do not create a "
-                    + "new empty entry.");
-            return null;
-        }
-        return insertEmptySubInfoRecord(iccId, null, slotIndex,
-                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
-    }
-
-    Uri insertEmptySubInfoRecord(String uniqueId, String displayName, int slotIndex,
-            int subscriptionType) {
-        ContentResolver resolver = mContext.getContentResolver();
-        ContentValues value = new ContentValues();
-        value.put(SubscriptionManager.ICC_ID, uniqueId);
-        int color = getUnusedColor(mContext.getOpPackageName(), mContext.getAttributionTag());
-        // default SIM color differs between slots
-        value.put(SubscriptionManager.HUE, color);
-        value.put(SubscriptionManager.SIM_SLOT_INDEX, slotIndex);
-        value.put(SubscriptionManager.CARRIER_NAME, "");
-        value.put(SubscriptionManager.CARD_ID, uniqueId);
-        value.put(SubscriptionManager.SUBSCRIPTION_TYPE, subscriptionType);
-        if (!TextUtils.isEmpty(displayName)) {
-            value.put(SubscriptionManager.DISPLAY_NAME, displayName);
-        }
-        if (!isSubscriptionForRemoteSim(subscriptionType)) {
-            UiccCard card = mUiccController.getUiccCardForPhone(slotIndex);
-            if (card != null) {
-                String cardId = card.getCardId();
-                if (cardId != null) {
-                    value.put(SubscriptionManager.CARD_ID, cardId);
-                }
-            }
-            UiccSlot slot = mUiccController.getUiccSlotForPhone(slotIndex);
-            if (slot != null) {
-                value.put(SubscriptionManager.PORT_INDEX, slot.getPortIndexFromIccId(uniqueId));
-            }
-        }
-        value.put(SubscriptionManager.ALLOWED_NETWORK_TYPES,
-                "user=" + RadioAccessFamily.getRafFromNetworkType(
-                        RILConstants.PREFERRED_NETWORK_MODE));
-
-        value.put(SubscriptionManager.USAGE_SETTING,
-                SubscriptionManager.USAGE_SETTING_UNKNOWN);
-
-        Uri uri = resolver.insert(SubscriptionManager.CONTENT_URI, value);
-
-        // Refresh the Cache of Active Subscription Info List
-        refreshCachedActiveSubscriptionInfoList();
-
-        return uri;
-    }
-
-    /**
-     * Generate and set carrier text based on input parameters
-     * @param showPlmn flag to indicate if plmn should be included in carrier text
-     * @param plmn plmn to be included in carrier text
-     * @param showSpn flag to indicate if spn should be included in carrier text
-     * @param spn spn to be included in carrier text
-     * @return true if carrier text is set, false otherwise
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public boolean setPlmnSpn(int slotIndex, boolean showPlmn, String plmn, boolean showSpn,
-                              String spn) {
-        synchronized (mLock) {
-            int subId = getSubId(slotIndex);
-            if (mContext.getPackageManager().resolveContentProvider(
-                    SubscriptionManager.CONTENT_URI.getAuthority(), 0) == null ||
-                    !SubscriptionManager.isValidSubscriptionId(subId)) {
-                // No place to store this info. Notify registrants of the change anyway as they
-                // might retrieve the SPN/PLMN text from the SST sticky broadcast.
-                // TODO: This can be removed once SubscriptionController is not running on devices
-                // that don't need it, such as TVs.
-                if (DBG) logd("[setPlmnSpn] No valid subscription to store info");
-                notifySubscriptionInfoChanged();
-                return false;
-            }
-            String carrierText = "";
-            if (showPlmn) {
-                carrierText = plmn;
-                if (showSpn) {
-                    // Need to show both plmn and spn if both are not same.
-                    if(!Objects.equals(spn, plmn)) {
-                        String separator = mContext.getString(
-                                com.android.internal.R.string.kg_text_message_separator).toString();
-                        carrierText = new StringBuilder().append(carrierText).append(separator)
-                                .append(spn).toString();
-                    }
-                }
-            } else if (showSpn) {
-                carrierText = spn;
-            }
-            setCarrierText(carrierText, subId);
-            return true;
-        }
-    }
-
-    /**
-     * Set carrier text by simInfo index
-     * @param text new carrier text
-     * @param subId the unique SubInfoRecord index in database
-     * @return the number of records updated
-     */
-    private int setCarrierText(String text, int subId) {
-        if (DBG) logd("[setCarrierText]+ text:" + text + " subId:" + subId);
-
-        enforceModifyPhoneState("setCarrierText");
-
-        // Now that all security checks passes, perform the operation as ourselves.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            boolean update = true;
-            int result = 0;
-            SubscriptionInfo subInfo = getSubscriptionInfo(subId);
-            if (subInfo != null) {
-                update = !TextUtils.equals(text, subInfo.getCarrierName());
-            }
-            if (update) {
-                ContentValues value = new ContentValues(1);
-                value.put(SubscriptionManager.CARRIER_NAME, text);
-
-                result = mContext.getContentResolver().update(
-                        SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
-
-                // Refresh the Cache of Active Subscription Info List
-                refreshCachedActiveSubscriptionInfoList();
-
-                notifySubscriptionInfoChanged();
-            } else {
-                if (DBG) logd("[setCarrierText]: no value update");
-            }
-            return result;
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    /**
-     * Set SIM color tint by simInfo index
-     *
-     * @param subId the unique SubInfoRecord index in database
-     * @param tint the tint color of the SIM
-     *
-     * @return the number of records updated
-     */
-    @Override
-    public int setIconTint(int subId, int tint) {
-        if (DBG) logd("[setIconTint]+ tint:" + tint + " subId:" + subId);
-
-        enforceModifyPhoneState("setIconTint");
-
-        // Now that all security checks passes, perform the operation as ourselves.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            validateSubId(subId);
-            ContentValues value = new ContentValues(1);
-            value.put(SubscriptionManager.HUE, tint);
-            if (DBG) logd("[setIconTint]- tint:" + tint + " set");
-
-            int result = mContext.getContentResolver().update(
-                    SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
-
-            // Refresh the Cache of Active Subscription Info List
-            refreshCachedActiveSubscriptionInfoList();
-
-            notifySubscriptionInfoChanged();
-
-            return result;
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    /**
-     * This is only for internal use and the returned priority is arbitrary. The idea is to give a
-     * higher value to name source that has higher priority to override other name sources.
-     * @param nameSource Source of display name
-     * @return int representing the priority. Higher value means higher priority.
-     */
-    public static int getNameSourcePriority(@SimDisplayNameSource int nameSource) {
-        int index = Arrays.asList(
-                SubscriptionManager.NAME_SOURCE_CARRIER_ID,
-                SubscriptionManager.NAME_SOURCE_SIM_PNN,
-                SubscriptionManager.NAME_SOURCE_SIM_SPN,
-                SubscriptionManager.NAME_SOURCE_CARRIER,
-                SubscriptionManager.NAME_SOURCE_USER_INPUT // user has highest priority.
-        ).indexOf(nameSource);
-        return (index < 0) ? 0 : index;
-    }
-
-    /**
-     * Validate whether the NAME_SOURCE_SIM_PNN, NAME_SOURCE_SIM_SPN and
-     * NAME_SOURCE_CARRIER exist or not.
-     */
-    @VisibleForTesting
-    public boolean isExistingNameSourceStillValid(SubscriptionInfo subInfo) {
-
-        int subId = subInfo.getSubscriptionId();
-        int phoneId = getPhoneId(subInfo.getSubscriptionId());
-
-        Phone phone = PhoneFactory.getPhone(phoneId);
-        if (phone == null) {
-            return true;
-        }
-
-        String spn;
-
-        switch (subInfo.getDisplayNameSource()) {
-            case SubscriptionManager.NAME_SOURCE_SIM_PNN:
-                String pnn = phone.getPlmn();
-                return !TextUtils.isEmpty(pnn);
-            case SubscriptionManager.NAME_SOURCE_SIM_SPN:
-                spn = getServiceProviderName(phoneId);
-                return !TextUtils.isEmpty(spn);
-            case SubscriptionManager.NAME_SOURCE_CARRIER:
-                // Can not validate eSIM since it should not override with a lower priority source
-                // if the name is actually coming from eSIM and not from carrier config.
-                if (subInfo.isEmbedded()) {
-                    return true;
-                }
-                CarrierConfigManager configLoader =
-                        mContext.getSystemService(CarrierConfigManager.class);
-                PersistableBundle config =
-                        configLoader.getConfigForSubId(subId);
-                if (config == null) {
-                    return true;
-                }
-                boolean isCarrierNameOverride = config.getBoolean(
-                        CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL, false);
-                String carrierName = config.getString(
-                        CarrierConfigManager.KEY_CARRIER_NAME_STRING);
-                spn = getServiceProviderName(phoneId);
-                return isCarrierNameOverride
-                        || (TextUtils.isEmpty(spn) && !TextUtils.isEmpty(carrierName));
-            case SubscriptionManager.NAME_SOURCE_CARRIER_ID:
-            case SubscriptionManager.NAME_SOURCE_USER_INPUT:
-                return true;
-        }
-        return false;
-    }
-
-    @VisibleForTesting
-    public String getServiceProviderName(int phoneId) {
-        UiccProfile profile = mUiccController.getUiccProfileForPhone(phoneId);
-        if (profile == null) {
-            return null;
-        }
-        return profile.getServiceProviderName();
-    }
-
-    /**
-     * Set display name by simInfo index with name source
-     * @param displayName the display name of SIM card
-     * @param subId the unique SubInfoRecord index in database
-     * @param nameSource SIM display name source
-     * @return the number of records updated
-     */
-    @Override
-    public int setDisplayNameUsingSrc(String displayName, int subId,
-                                      @SimDisplayNameSource int nameSource) {
-        if (DBG) {
-            logd("[setDisplayName]+  displayName:" + displayName + " subId:" + subId
-                + " nameSource:" + nameSource);
-        }
-
-        enforceModifyPhoneState("setDisplayNameUsingSrc");
-
-        // Now that all security checks passes, perform the operation as ourselves.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            validateSubId(subId);
-            List<SubscriptionInfo> allSubInfo = getSubInfo(null, null);
-            // if there is no sub in the db, return 0 since subId does not exist in db
-            if (allSubInfo == null || allSubInfo.isEmpty()) return 0;
-            for (SubscriptionInfo subInfo : allSubInfo) {
-                int subInfoNameSource = subInfo.getDisplayNameSource();
-                boolean isHigherPriority = (getNameSourcePriority(subInfoNameSource)
-                        > getNameSourcePriority(nameSource));
-                boolean isEqualPriorityAndName = (getNameSourcePriority(subInfoNameSource)
-                        == getNameSourcePriority(nameSource))
-                        && (TextUtils.equals(displayName, subInfo.getDisplayName()));
-                if (subInfo.getSubscriptionId() == subId
-                        && isExistingNameSourceStillValid(subInfo)
-                        && (isHigherPriority || isEqualPriorityAndName)) {
-                    logd("Name source " + subInfoNameSource + "'s priority "
-                            + getNameSourcePriority(subInfoNameSource) + " is greater than "
-                            + "name source " + nameSource + "'s priority "
-                            + getNameSourcePriority(nameSource) + ", return now.");
-                    return 0;
-                }
-            }
-            String nameToSet;
-            if (TextUtils.isEmpty(displayName) || displayName.trim().length() == 0) {
-                nameToSet = mTelephonyManager.getSimOperatorName(subId);
-                if (TextUtils.isEmpty(nameToSet)) {
-                    if (nameSource == SubscriptionManager.NAME_SOURCE_USER_INPUT
-                            && SubscriptionManager.isValidSlotIndex(getSlotIndex(subId))) {
-                        nameToSet = "CARD " + (getSlotIndex(subId) + 1);
-                    } else {
-                        nameToSet = mContext.getString(SubscriptionManager.DEFAULT_NAME_RES);
-                    }
-                }
-            } else {
-                nameToSet = displayName;
-            }
-            ContentValues value = new ContentValues(1);
-            value.put(SubscriptionManager.DISPLAY_NAME, nameToSet);
-            if (nameSource >= SubscriptionManager.NAME_SOURCE_CARRIER_ID) {
-                if (DBG) logd("Set nameSource=" + nameSource);
-                value.put(SubscriptionManager.NAME_SOURCE, nameSource);
-            }
-            if (DBG) logd("[setDisplayName]- mDisplayName:" + nameToSet + " set");
-
-            // Update the nickname on the eUICC chip if it's an embedded subscription.
-            SubscriptionInfo sub = getSubscriptionInfo(subId);
-            if (sub != null && sub.isEmbedded()) {
-                // Ignore the result.
-                int cardId = sub.getCardId();
-                if (DBG) logd("Updating embedded sub nickname on cardId: " + cardId);
-                EuiccManager euiccManager = ((EuiccManager)
-                        mContext.getSystemService(Context.EUICC_SERVICE)).createForCardId(cardId);
-                euiccManager.updateSubscriptionNickname(subId, displayName,
-                        // This PendingIntent simply fulfills the requirement to pass in a callback;
-                        // we don't care about the result (hence 0 requestCode and no action
-                        // specified on the intent).
-                        PendingIntent.getService(
-                            mContext, 0 /* requestCode */, new Intent(),
-                                PendingIntent.FLAG_IMMUTABLE /* flags */));
-            }
-
-            int result = updateDatabase(value, subId, true);
-
-            // Refresh the Cache of Active Subscription Info List
-            refreshCachedActiveSubscriptionInfoList();
-
-            notifySubscriptionInfoChanged();
-
-            return result;
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    /**
-     * Set phone number by subId
-     * @param number the phone number of the SIM
-     * @param subId the unique SubInfoRecord index in database
-     * @return the number of records updated
-     */
-    @Override
-    public int setDisplayNumber(String number, int subId) {
-        if (DBG) logd("[setDisplayNumber]+ subId:" + subId);
-
-        enforceModifyPhoneState("setDisplayNumber");
-
-        // Now that all security checks passes, perform the operation as ourselves.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            validateSubId(subId);
-            int result = 0;
-            int phoneId = getPhoneId(subId);
-
-            if (number == null || phoneId < 0 ||
-                    phoneId >= mTelephonyManager.getPhoneCount()) {
-                if (DBG) logd("[setDisplayNumber]- fail");
-                return -1;
-            }
-            boolean update = true;
-            SubscriptionInfo subInfo = getSubscriptionInfo(subId);
-            if (subInfo != null) {
-                update = !TextUtils.equals(subInfo.getNumber(), number);
-            }
-            if (update) {
-                ContentValues value = new ContentValues(1);
-                value.put(SubscriptionManager.NUMBER, number);
-
-                // This function had a call to update number on the SIM (Phone.setLine1Number()) but
-                // that was removed as there doesn't seem to be a reason for that. If it is added
-                // back, watch out for deadlocks.
-                result = mContext.getContentResolver().update(
-                        SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
-                if (DBG) logd("[setDisplayNumber]- update result :" + result);
-                // Refresh the Cache of Active Subscription Info List
-                refreshCachedActiveSubscriptionInfoList();
-                notifySubscriptionInfoChanged();
-            } else {
-                if (DBG) logd("[setDisplayNumber]: no value update");
-            }
-            return result;
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    /**
-     * Set the EHPLMNs and HPLMNs associated with the subscription.
-     */
-    public void setAssociatedPlmns(String[] ehplmns, String[] hplmns, int subId) {
-        if (DBG) logd("[setAssociatedPlmns]+ subId:" + subId);
-
-        validateSubId(subId);
-        int phoneId = getPhoneId(subId);
-
-        if (phoneId < 0 || phoneId >= mTelephonyManager.getPhoneCount()) {
-            if (DBG) logd("[setAssociatedPlmns]- fail");
-            return;
-        }
-
-        // remove trailing empty strings which will also get stripped from
-        // SubscriptionInfo.getEhplmns() and SubscriptionInfo.getHplmns()
-        String formattedEhplmns = ehplmns == null ? "" :
-                Arrays.stream(ehplmns).filter(s -> s != null && !s.isEmpty())
-                        .collect(Collectors.joining(","));
-        String formattedHplmns = hplmns == null ? "" :
-                Arrays.stream(hplmns).filter(s -> s != null && !s.isEmpty())
-                        .collect(Collectors.joining(","));
-        boolean noChange = false;
-        SubscriptionInfo subInfo = getSubscriptionInfo(subId);
-        if (subInfo != null) {
-            noChange = (ehplmns == null && subInfo.getEhplmns().isEmpty())
-                    || String.join(",", subInfo.getEhplmns()).equals(formattedEhplmns);
-            noChange = noChange && (hplmns == null && subInfo.getHplmns().isEmpty())
-                    || String.join(",", subInfo.getHplmns()).equals(formattedHplmns);
-        }
-        if (!noChange) {
-            ContentValues value = new ContentValues(2);
-            value.put(SubscriptionManager.EHPLMNS, formattedEhplmns);
-            value.put(SubscriptionManager.HPLMNS, formattedHplmns);
-
-            int count = mContext.getContentResolver().update(
-                    SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
-            if (DBG) logd("[setAssociatedPlmns]- update result :" + count);
-            // Refresh the Cache of Active Subscription Info List
-            refreshCachedActiveSubscriptionInfoList();
-            notifySubscriptionInfoChanged();
-        } else {
-            if (DBG) logd("[setAssociatedPlmns]+ subId:" + subId + "no value update");
-        }
-    }
-
-    /**
-     * Set data roaming by simInfo index
-     * @param roaming 0:Don't allow data when roaming, 1:Allow data when roaming
-     * @param subId the unique SubInfoRecord index in database
-     * @return the number of records updated
-     */
-    @Override
-    public int setDataRoaming(int roaming, int subId) {
-        if (DBG) logd("[setDataRoaming]+ roaming:" + roaming + " subId:" + subId);
-
-        enforceModifyPhoneState("setDataRoaming");
-
-        // Now that all security checks passes, perform the operation as ourselves.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            validateSubId(subId);
-            if (roaming < 0) {
-                if (DBG) logd("[setDataRoaming]- fail");
-                return -1;
-            }
-            ContentValues value = new ContentValues(1);
-            value.put(SubscriptionManager.DATA_ROAMING, roaming);
-            if (DBG) logd("[setDataRoaming]- roaming:" + roaming + " set");
-
-            int result = updateDatabase(value, subId, true);
-
-            // Refresh the Cache of Active Subscription Info List
-            refreshCachedActiveSubscriptionInfoList();
-
-            notifySubscriptionInfoChanged();
-
-            return result;
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    /**
-     * Set device to device status sharing preference
-     * @param sharing the sharing preference to set
-     * @param subId
-     * @return the number of records updated
-     */
-    @Override
-    public int setDeviceToDeviceStatusSharing(int sharing, int subId) {
-        if (DBG) logd("[setDeviceToDeviceStatusSharing]- sharing:" + sharing + " subId:" + subId);
-
-        enforceModifyPhoneState("setDeviceToDeviceStatusSharing");
-
-        // Now that all security checks passes, perform the operation as ourselves.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            validateSubId(subId);
-            if (sharing < 0) {
-                if (DBG) logd("[setDeviceToDeviceStatusSharing]- fail");
-                return -1;
-            }
-            ContentValues value = new ContentValues(1);
-            value.put(SubscriptionManager.D2D_STATUS_SHARING, sharing);
-            if (DBG) logd("[setDeviceToDeviceStatusSharing]- sharing:" + sharing + " set");
-
-            int result = updateDatabase(value, subId, true);
-
-            // Refresh the Cache of Active Subscription Info List
-            refreshCachedActiveSubscriptionInfoList();
-
-            notifySubscriptionInfoChanged();
-
-            return result;
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    /**
-     * Set contacts that allow device to device status sharing.
-     * @param contacts contacts to set
-     * @param subscriptionId
-     * @return the number of records updated
-     */
-    @Override
-    public int setDeviceToDeviceStatusSharingContacts(String contacts, int subscriptionId) {
-        if (DBG) {
-            logd("[setDeviceToDeviceStatusSharingContacts]- contacts:" + contacts
-                    + " subId:" + subscriptionId);
-        }
-
-        enforceModifyPhoneState("setDeviceToDeviceStatusSharingContacts");
-
-        // Now that all security checks passes, perform the operation as ourselves.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            validateSubId(subscriptionId);
-            ContentValues value = new ContentValues(1);
-            value.put(SubscriptionManager.D2D_STATUS_SHARING_SELECTED_CONTACTS, contacts);
-            if (DBG) {
-                logd("[setDeviceToDeviceStatusSharingContacts]- contacts:" + contacts
-                        + " set");
-            }
-
-            int result = updateDatabase(value, subscriptionId, true);
-
-            // Refresh the Cache of Active Subscription Info List
-            refreshCachedActiveSubscriptionInfoList();
-
-            notifySubscriptionInfoChanged();
-
-            return result;
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    public void syncGroupedSetting(int refSubId) {
-        logd("syncGroupedSetting");
-        try (Cursor cursor = mContext.getContentResolver().query(
-                SubscriptionManager.CONTENT_URI, null,
-                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=?",
-                new String[] {String.valueOf(refSubId)}, null)) {
-            if (cursor == null || !cursor.moveToFirst()) {
-                logd("[syncGroupedSetting] failed. Can't find refSubId " + refSubId);
-                return;
-            }
-
-            ContentValues values = new ContentValues(GROUP_SHARING_PROPERTIES.size());
-            for (String propKey : GROUP_SHARING_PROPERTIES) {
-                copyDataFromCursorToContentValue(propKey, cursor, values);
-            }
-            updateDatabase(values, refSubId, true);
-        }
-    }
-
-    private void copyDataFromCursorToContentValue(String propKey, Cursor cursor,
-            ContentValues values) {
-        int columnIndex = cursor.getColumnIndex(propKey);
-        if (columnIndex == -1) {
-            logd("[copyDataFromCursorToContentValue] can't find column " + propKey);
-            return;
-        }
-
-        switch (propKey) {
-            case SubscriptionManager.ENHANCED_4G_MODE_ENABLED:
-            case SubscriptionManager.VT_IMS_ENABLED:
-            case SubscriptionManager.WFC_IMS_ENABLED:
-            case SubscriptionManager.WFC_IMS_MODE:
-            case SubscriptionManager.WFC_IMS_ROAMING_MODE:
-            case SubscriptionManager.WFC_IMS_ROAMING_ENABLED:
-            case SubscriptionManager.DATA_ROAMING:
-            case SubscriptionManager.IMS_RCS_UCE_ENABLED:
-            case SubscriptionManager.CROSS_SIM_CALLING_ENABLED:
-            case SubscriptionManager.NR_ADVANCED_CALLING_ENABLED:
-            case SubscriptionManager.USER_HANDLE:
-                values.put(propKey, cursor.getInt(columnIndex));
-                break;
-            case SubscriptionManager.DISPLAY_NAME:
-            case SubscriptionManager.ENABLED_MOBILE_DATA_POLICIES:
-                values.put(propKey, cursor.getString(columnIndex));
-                break;
-            default:
-                loge("[copyDataFromCursorToContentValue] invalid propKey " + propKey);
-        }
-    }
-
-    // TODO: replace all updates with this helper method.
-    private int updateDatabase(ContentValues value, int subId, boolean updateEntireGroup) {
-        List<SubscriptionInfo> infoList = getSubscriptionsInGroup(getGroupUuid(subId),
-                mContext.getOpPackageName(), mContext.getAttributionTag());
-        if (!updateEntireGroup || infoList == null || infoList.size() == 0) {
-            // Only update specified subscriptions.
-            return mContext.getContentResolver().update(
-                    SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
-        } else {
-            // Update all subscriptions in the same group.
-            int[] subIdList = new int[infoList.size()];
-            for (int i = 0; i < infoList.size(); i++) {
-                subIdList[i] = infoList.get(i).getSubscriptionId();
-            }
-            return mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
-                    value, getSelectionForSubIdList(subIdList), null);
-        }
-    }
-
-    /**
-     * Set carrier id by subId
-     * @param carrierId the subscription carrier id.
-     * @param subId the unique SubInfoRecord index in database
-     * @return the number of records updated
-     *
-     * @see TelephonyManager#getSimCarrierId()
-     */
-    public int setCarrierId(int carrierId, int subId) {
-        if (DBG) logd("[setCarrierId]+ carrierId:" + carrierId + " subId:" + subId);
-
-        enforceModifyPhoneState("setCarrierId");
-
-        // Now that all security checks passes, perform the operation as ourselves.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            validateSubId(subId);
-            int result = 0;
-            boolean update = true;
-            SubscriptionInfo subInfo = getSubscriptionInfo(subId);
-            if (subInfo != null) {
-                update = subInfo.getCarrierId() != carrierId;
-            }
-            if (update) {
-                ContentValues value = new ContentValues(1);
-                value.put(SubscriptionManager.CARRIER_ID, carrierId);
-                result = mContext.getContentResolver().update(
-                        SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
-
-                // Refresh the Cache of Active Subscription Info List
-                refreshCachedActiveSubscriptionInfoList();
-
-                notifySubscriptionInfoChanged();
-            } else {
-                if (DBG) logd("[setCarrierId]: no value update");
-            }
-            return result;
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    /**
-     * Set MCC/MNC by subscription ID
-     * @param mccMnc MCC/MNC associated with the subscription
-     * @param subId the unique SubInfoRecord index in database
-     * @return the number of records updated
-     */
-    public int setMccMnc(String mccMnc, int subId) {
-        String mccString = mccMnc.substring(0, 3);
-        String mncString = mccMnc.substring(3);
-        int mcc = 0;
-        int mnc = 0;
-        try {
-            mcc = Integer.parseInt(mccString);
-            mnc = Integer.parseInt(mncString);
-        } catch (NumberFormatException e) {
-            loge("[setMccMnc] - couldn't parse mcc/mnc: " + mccMnc);
-        }
-        SubscriptionInfo subInfo = getSubscriptionInfo(subId);
-        // check if there are any update
-        boolean update = true;
-        if (subInfo != null) {
-            update = (subInfo.getMcc() != mcc) || (subInfo.getMnc() != mnc)
-                    || !mccString.equals(subInfo.getMccString())
-                    || !mncString.equals(subInfo.getMncString());
-        }
-        int result = 0;
-        if (update) {
-            ContentValues value = new ContentValues(4);
-            value.put(SubscriptionManager.MCC, mcc);
-            value.put(SubscriptionManager.MNC, mnc);
-            value.put(SubscriptionManager.MCC_STRING, mccString);
-            value.put(SubscriptionManager.MNC_STRING, mncString);
-
-            result = mContext.getContentResolver().update(
-                    SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
-            if (DBG) logd("[setMccMnc]+ mcc/mnc:" + mcc + "/" + mnc + " subId:" + subId);
-            // Refresh the Cache of Active Subscription Info List
-            refreshCachedActiveSubscriptionInfoList();
-            notifySubscriptionInfoChanged();
-        } else {
-            if (DBG) logd("[setMccMnc] - no values update");
-        }
-        return result;
-    }
-
-    /**
-     * Scrub given IMSI on production builds.
-     */
-    private String scrubImsi(String imsi) {
-        if (Build.IS_ENG) {
-            return imsi;
-        } else if (imsi != null) {
-            return imsi.substring(0, Math.min(6, imsi.length())) + "...";
-        } else {
-            return "null";
-        }
-    }
-
-    /**
-     * Set IMSI by subscription ID
-     * @param imsi IMSI (International Mobile Subscriber Identity)
-     * @return the number of records updated
-     */
-    public int setImsi(String imsi, int subId) {
-        if (DBG) logd("[setImsi]+ imsi:" + scrubImsi(imsi) + " subId:" + subId);
-        boolean update = true;
-        int result = 0;
-        SubscriptionInfo subInfo = getSubscriptionInfo(subId);
-        if (subInfo != null) {
-            update = !TextUtils.equals(getImsiPrivileged(subId),imsi);
-        }
-
-        if (update) {
-            ContentValues value = new ContentValues(1);
-            value.put(SubscriptionManager.IMSI, imsi);
-            result = mContext.getContentResolver().update(
-                    SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
-            // Refresh the Cache of Active Subscription Info List
-            refreshCachedActiveSubscriptionInfoList();
-
-            notifySubscriptionInfoChanged();
-        } else {
-            if (DBG) logd("[setImsi]: no value update");
-        }
-        return result;
-    }
-
-    /**
-     * Set uicc applications being enabled or disabled.
-     * @param enabled whether uicc applications are enabled or disabled.
-     */
-    public void setUiccApplicationsEnabled(boolean enabled, int subId) {
-        if (DBG) logd("[setUiccApplicationsEnabled]+ enabled:" + enabled + " subId:" + subId);
-
-        enforceModifyPhoneState("setUiccApplicationsEnabled");
-
-        long identity = Binder.clearCallingIdentity();
-        try {
-            ContentValues value = new ContentValues(1);
-            value.put(SubscriptionManager.UICC_APPLICATIONS_ENABLED, enabled);
-
-            mContext.getContentResolver().update(SubscriptionManager.getUriForSubscriptionId(subId),
-                    value, null, null);
-
-            // Refresh the Cache of Active Subscription Info List
-            refreshCachedActiveSubscriptionInfoList();
-
-            notifyUiccAppsEnableChanged();
-            notifySubscriptionInfoChanged();
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    /**
-     * Register to change of uicc applications enablement changes.
-     * @param notifyNow whether to notify target upon registration.
-     */
-    public void registerForUiccAppsEnabled(Handler handler, int what, Object object,
-            boolean notifyNow) {
-        mUiccAppsEnableChangeRegList.addUnique(handler, what, object);
-        if (notifyNow) {
-            handler.obtainMessage(what, object).sendToTarget();
-        }
-    }
-
-    /**
-     * Unregister to change of uicc applications enablement changes.
-     */
-    public void unregisterForUiccAppsEnabled(Handler handler) {
-        mUiccAppsEnableChangeRegList.remove(handler);
-    }
-
-    private void notifyUiccAppsEnableChanged() {
-        mUiccAppsEnableChangeRegList.notifyRegistrants();
-    }
-
-    /**
-     * Get IMSI by subscription ID
-     * For active subIds, this will always return the corresponding imsi
-     * For inactive subIds, once they are activated once, even if they are deactivated at the time
-     *   of calling this function, the corresponding imsi will be returned
-     * When calling this method, the permission check should have already been done to allow
-     *   only privileged read
-     *
-     * @return imsi
-     */
-    public String getImsiPrivileged(int subId) {
-        try (Cursor cursor = mContext.getContentResolver().query(
-                SubscriptionManager.CONTENT_URI, null,
-                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=?",
-                new String[] {String.valueOf(subId)}, null)) {
-            String imsi = null;
-            if (cursor != null) {
-                if (cursor.moveToNext()) {
-                    imsi = getOptionalStringFromCursor(cursor, SubscriptionManager.IMSI,
-                            /*defaultVal*/ null);
-                }
-            } else {
-                logd("getImsiPrivileged: failed to retrieve imsi.");
-            }
-
-            return imsi;
-        }
-    }
-
-    /**
-     * Set ISO country code by subscription ID
-     * @param iso iso country code associated with the subscription
-     * @param subId the unique SubInfoRecord index in database
-     * @return the number of records updated
-     */
-    public int setCountryIso(String iso, int subId) {
-        if (DBG) logd("[setCountryIso]+ iso:" + iso + " subId:" + subId);
-        boolean update = true;
-        int result = 0;
-        SubscriptionInfo subInfo = getSubscriptionInfo(subId);
-        if (subInfo != null) {
-            update = !TextUtils.equals(subInfo.getCountryIso(), iso);
-        }
-        if (update) {
-            ContentValues value = new ContentValues();
-            value.put(SubscriptionManager.ISO_COUNTRY_CODE, iso);
-
-            result = mContext.getContentResolver().update(
-                    SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
-            // Refresh the Cache of Active Subscription Info List
-            refreshCachedActiveSubscriptionInfoList();
-
-            notifySubscriptionInfoChanged();
-        } else {
-            if (DBG) logd("[setCountryIso]: no value update");
-        }
-        return result;
-    }
-
-    @Override
-    public int getSlotIndex(int subId) {
-        if (VDBG) printStackTrace("[getSlotIndex] subId=" + subId);
-
-        if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
-            subId = getDefaultSubId();
-        }
-        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
-            if (DBG) logd("[getSlotIndex]- subId invalid");
-            return SubscriptionManager.INVALID_SIM_SLOT_INDEX;
-        }
-
-        int size = mSlotIndexToSubIds.size();
-
-        if (size == 0) {
-            if (DBG) logd("[getSlotIndex]- size == 0, return SIM_NOT_INSERTED instead");
-            return SubscriptionManager.SIM_NOT_INSERTED;
-        }
-
-        for (Entry<Integer, ArrayList<Integer>> entry : mSlotIndexToSubIds.entrySet()) {
-            int sim = entry.getKey();
-            ArrayList<Integer> subs = entry.getValue();
-
-            if (subs != null && subs.contains(subId)) {
-                if (VDBG) logv("[getSlotIndex]- return = " + sim);
-                return sim;
-            }
-        }
-
-        if (DBG) logd("[getSlotIndex]- return fail");
-        return SubscriptionManager.INVALID_SIM_SLOT_INDEX;
-    }
-
-    /**
-     * Return the subIds for specified slot Id.
-     * @deprecated
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @Deprecated
-    public int[] getSubIds(int slotIndex) {
-        if (VDBG) printStackTrace("[getSubId]+ slotIndex=" + slotIndex);
-
-        // Map default slotIndex to the current default subId.
-        // TODO: Not used anywhere sp consider deleting as it's somewhat nebulous
-        // as a slot maybe used for multiple different type of "connections"
-        // such as: voice, data and sms. But we're doing the best we can and using
-        // getDefaultSubId which makes a best guess.
-        if (slotIndex == SubscriptionManager.DEFAULT_SIM_SLOT_INDEX) {
-            slotIndex = getSlotIndex(getDefaultSubId());
-            if (VDBG) logd("[getSubId] map default slotIndex=" + slotIndex);
-        }
-
-        // Check that we have a valid slotIndex or the slotIndex is for a remote SIM (remote SIM
-        // uses special slot index that may be invalid otherwise)
-        if (!SubscriptionManager.isValidSlotIndex(slotIndex)
-                && slotIndex != SubscriptionManager.SLOT_INDEX_FOR_REMOTE_SIM_SUB) {
-            if (DBG) logd("[getSubId]- invalid slotIndex=" + slotIndex);
-            return null;
-        }
-
-        // Check if we've got any SubscriptionInfo records using slotIndexToSubId as a surrogate.
-        int size = mSlotIndexToSubIds.size();
-        if (size == 0) {
-            if (VDBG) {
-                logd("[getSubId]- sSlotIndexToSubIds.size == 0, return null slotIndex="
-                        + slotIndex);
-            }
-            return null;
-        }
-
-        // Convert ArrayList to array
-        ArrayList<Integer> subIds = mSlotIndexToSubIds.getCopy(slotIndex);
-        if (subIds != null && subIds.size() > 0) {
-            int[] subIdArr = new int[subIds.size()];
-            for (int i = 0; i < subIds.size(); i++) {
-                subIdArr[i] = subIds.get(i);
-            }
-            if (VDBG) logd("[getSubId]- subIdArr=" + Arrays.toString(subIdArr));
-            return subIdArr;
-        } else {
-            if (DBG) logd("[getSubId]- numSubIds == 0, return null slotIndex=" + slotIndex);
-            return null;
-        }
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @Override
-    public int getPhoneId(int subId) {
-        if (VDBG) printStackTrace("[getPhoneId] subId=" + subId);
-        int phoneId;
-
-        if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
-            subId = getDefaultSubId();
-            if (DBG) logd("[getPhoneId] asked for default subId=" + subId);
-        }
-
-        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
-            if (VDBG) {
-                logdl("[getPhoneId]- invalid subId return="
-                        + SubscriptionManager.INVALID_PHONE_INDEX);
-            }
-            return SubscriptionManager.INVALID_PHONE_INDEX;
-        }
-
-        int size = mSlotIndexToSubIds.size();
-        if (size == 0) {
-            phoneId = mDefaultPhoneId;
-            if (VDBG) logdl("[getPhoneId]- no sims, returning default phoneId=" + phoneId);
-            return phoneId;
-        }
-
-        // FIXME: Assumes phoneId == slotIndex
-        for (Entry<Integer, ArrayList<Integer>> entry: mSlotIndexToSubIds.entrySet()) {
-            int sim = entry.getKey();
-            ArrayList<Integer> subs = entry.getValue();
-
-            if (subs != null && subs.contains(subId)) {
-                if (VDBG) logdl("[getPhoneId]- found subId=" + subId + " phoneId=" + sim);
-                return sim;
-            }
-        }
-
-        phoneId = mDefaultPhoneId;
-        if (VDBG) {
-            logd("[getPhoneId]- subId=" + subId + " not found return default phoneId=" + phoneId);
-        }
-        return phoneId;
-
-    }
-
-    /**
-     * @return the number of records cleared
-     */
-    public int clearSubInfo() {
-        enforceModifyPhoneState("clearSubInfo");
-
-        // Now that all security checks passes, perform the operation as ourselves.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            int size = mSlotIndexToSubIds.size();
-
-            if (size == 0) {
-                if (DBG) logdl("[clearSubInfo]- no simInfo size=" + size);
-                return 0;
-            }
-
-            mSlotIndexToSubIds.clear();
-            if (DBG) logdl("[clearSubInfo]- clear size=" + size);
-            return size;
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    private void logvl(String msg) {
-        logv(msg);
-        mLocalLog.log(msg);
-    }
-
-    private void logv(String msg) {
-        Rlog.v(LOG_TAG, msg);
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    protected void logdl(String msg) {
-        logd(msg);
-        mLocalLog.log(msg);
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private void logd(String msg) {
-        Rlog.d(LOG_TAG, msg);
-    }
-
-    private void logel(String msg) {
-        loge(msg);
-        mLocalLog.log(msg);
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private void loge(String msg) {
-        Rlog.e(LOG_TAG, msg);
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @Override
-    public int getDefaultSubId() {
-        int subId;
-        boolean isVoiceCapable = mTelephonyManager.isVoiceCapable();
-        if (isVoiceCapable) {
-            subId = getDefaultVoiceSubId();
-            if (VDBG) logdl("[getDefaultSubId] isVoiceCapable subId=" + subId);
-        } else {
-            subId = getDefaultDataSubId();
-            if (VDBG) logdl("[getDefaultSubId] NOT VoiceCapable subId=" + subId);
-        }
-        if (!isActiveSubId(subId)) {
-            subId = mDefaultFallbackSubId.get();
-            if (VDBG) logdl("[getDefaultSubId] NOT active use fall back subId=" + subId);
-        }
-        if (VDBG) logv("[getDefaultSubId]- value = " + subId);
-        return subId;
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @Override
-    public void setDefaultSmsSubId(int subId) {
-        enforceModifyPhoneState("setDefaultSmsSubId");
-
-        if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
-            throw new RuntimeException("setDefaultSmsSubId called with DEFAULT_SUB_ID");
-        }
-        if (DBG) logdl("[setDefaultSmsSubId] subId=" + subId);
-        setGlobalSetting(Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION, subId);
-        broadcastDefaultSmsSubIdChanged(subId);
-    }
-
-    private void broadcastDefaultSmsSubIdChanged(int subId) {
-        // Broadcast an Intent for default sms sub change
-        if (DBG) logdl("[broadcastDefaultSmsSubIdChanged] subId=" + subId);
-        Intent intent = new Intent(SubscriptionManager.ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED);
-        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
-        SubscriptionManager.putSubscriptionIdExtra(intent, subId);
-        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @Override
-    public int getDefaultSmsSubId() {
-        int subId = Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION,
-                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-        if (VDBG) logd("[getDefaultSmsSubId] subId=" + subId);
-        return subId;
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @Override
-    public void setDefaultVoiceSubId(int subId) {
-        enforceModifyPhoneState("setDefaultVoiceSubId");
-
-        if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
-            throw new RuntimeException("setDefaultVoiceSubId called with DEFAULT_SUB_ID");
-        }
-
-        logdl("[setDefaultVoiceSubId] subId=" + subId);
-
-        int previousDefaultSub = getDefaultSubId();
-
-        setGlobalSetting(Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION, subId);
-        broadcastDefaultVoiceSubIdChanged(subId);
-
-        PhoneAccountHandle newHandle =
-                subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID
-                        ? null : mTelephonyManager.getPhoneAccountHandleForSubscriptionId(
-                        subId);
-
-        TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
-
-        telecomManager.setUserSelectedOutgoingPhoneAccount(newHandle);
-        logd("[setDefaultVoiceSubId] requesting change to phoneAccountHandle=" + newHandle);
-
-        if (previousDefaultSub != getDefaultSubId()) {
-            sendDefaultChangedBroadcast(getDefaultSubId());
-            logd(String.format("[setDefaultVoiceSubId] change to subId=%d", getDefaultSubId()));
-        } else {
-            logd(String.format("[setDefaultVoiceSubId] default subId not changed. subId=%d",
-                    previousDefaultSub));
-        }
-    }
-
-    /**
-     * Broadcast intent of ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED.
-     * @hide
-     */
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    public void broadcastDefaultVoiceSubIdChanged(int subId) {
-        // Broadcast an Intent for default voice sub change
-        if (DBG) logdl("[broadcastDefaultVoiceSubIdChanged] subId=" + subId);
-        Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED);
-        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
-        SubscriptionManager.putSubscriptionIdExtra(intent, subId);
-        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @Override
-    public int getDefaultVoiceSubId() {
-        int subId = Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION,
-                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-        if (VDBG) logd("[getDefaultVoiceSubId] subId=" + subId);
-        return subId;
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @Override
-    public int getDefaultDataSubId() {
-        int subId = Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION,
-                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-        if (VDBG) logd("[getDefaultDataSubId] subId=" + subId);
-        return subId;
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @Override
-    public void setDefaultDataSubId(int subId) {
-        enforceModifyPhoneState("setDefaultDataSubId");
-
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
-                throw new RuntimeException("setDefaultDataSubId called with DEFAULT_SUB_ID");
-            }
-
-            ProxyController proxyController = ProxyController.getInstance();
-            int len = TelephonyManager.from(mContext).getActiveModemCount();
-            logdl("[setDefaultDataSubId] num phones=" + len + ", subId=" + subId);
-
-            if (SubscriptionManager.isValidSubscriptionId(subId)) {
-                // Only re-map modems if the new default data sub is valid
-                RadioAccessFamily[] rafs = new RadioAccessFamily[len];
-                boolean atLeastOneMatch = false;
-                for (int phoneId = 0; phoneId < len; phoneId++) {
-                    Phone phone = PhoneFactory.getPhone(phoneId);
-                    int raf;
-                    int id = phone.getSubId();
-                    if (id == subId) {
-                        // TODO Handle the general case of N modems and M subscriptions.
-                        raf = proxyController.getMaxRafSupported();
-                        atLeastOneMatch = true;
-                    } else {
-                        // TODO Handle the general case of N modems and M subscriptions.
-                        raf = proxyController.getMinRafSupported();
-                    }
-                    logdl("[setDefaultDataSubId] phoneId=" + phoneId + " subId=" + id + " RAF="
-                            + raf);
-                    rafs[phoneId] = new RadioAccessFamily(phoneId, raf);
-                }
-                if (atLeastOneMatch) {
-                    proxyController.setRadioCapability(rafs);
-                } else {
-                    if (DBG) logdl("[setDefaultDataSubId] no valid subId's found - not updating.");
-                }
-            }
-
-            int previousDefaultSub = getDefaultSubId();
-            setGlobalSetting(Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION, subId);
-            MultiSimSettingController.getInstance().notifyDefaultDataSubChanged();
-            broadcastDefaultDataSubIdChanged(subId);
-            if (previousDefaultSub != getDefaultSubId()) {
-                sendDefaultChangedBroadcast(getDefaultSubId());
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private void broadcastDefaultDataSubIdChanged(int subId) {
-        // Broadcast an Intent for default data sub change
-        if (DBG) logdl("[broadcastDefaultDataSubIdChanged] subId=" + subId);
-        Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
-        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
-        SubscriptionManager.putSubscriptionIdExtra(intent, subId);
-        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
-    }
-
-    /* Sets the default subscription. If only one sub is active that
-     * sub is set as default subId. If two or more  sub's are active
-     * the first sub is set as default subscription
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    protected void setDefaultFallbackSubId(int subId, int subscriptionType) {
-        if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
-            throw new RuntimeException("setDefaultSubId called with DEFAULT_SUB_ID");
-        }
-        if (DBG) {
-            logdl("[setDefaultFallbackSubId] subId=" + subId + ", subscriptionType="
-                    + subscriptionType);
-        }
-        int previousDefaultSub = getDefaultSubId();
-        if (isSubscriptionForRemoteSim(subscriptionType)) {
-            mDefaultFallbackSubId.set(subId);
-            return;
-        }
-        if (SubscriptionManager.isValidSubscriptionId(subId)) {
-            int phoneId = getPhoneId(subId);
-            if (phoneId >= 0 && (phoneId < mTelephonyManager.getPhoneCount()
-                    || mTelephonyManager.getSimCount() == 1)) {
-                if (DBG) logdl("[setDefaultFallbackSubId] set sDefaultFallbackSubId=" + subId);
-                mDefaultFallbackSubId.set(subId);
-                // Update MCC MNC device configuration information
-                String defaultMccMnc = mTelephonyManager.getSimOperatorNumericForPhone(phoneId);
-                MccTable.updateMccMncConfiguration(mContext, defaultMccMnc);
-            } else {
-                if (DBG) {
-                    logdl("[setDefaultFallbackSubId] not set invalid phoneId=" + phoneId
-                            + " subId=" + subId);
-                }
-            }
-        }
-        if (previousDefaultSub != getDefaultSubId()) {
-            sendDefaultChangedBroadcast(getDefaultSubId());
-        }
-    }
-
-    public void sendDefaultChangedBroadcast(int subId) {
-        // Broadcast an Intent for default sub change
-        int phoneId = SubscriptionManager.getPhoneId(subId);
-        Intent intent = new Intent(SubscriptionManager.ACTION_DEFAULT_SUBSCRIPTION_CHANGED);
-        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
-        SubscriptionManager.putPhoneIdAndSubIdExtra(intent, phoneId, subId);
-        if (DBG) {
-            logdl("[sendDefaultChangedBroadcast] broadcast default subId changed phoneId="
-                    + phoneId + " subId=" + subId);
-        }
-        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
-    }
-
-    /**
-     * Whether a subscription is opportunistic or not.
-     */
-    public boolean isOpportunistic(int subId) {
-        SubscriptionInfo info = getActiveSubscriptionInfo(subId, mContext.getOpPackageName(),
-                mContext.getAttributionTag());
-        return (info != null) && info.isOpportunistic();
-    }
-
-    // FIXME: We need we should not be assuming phoneId == slotIndex as it will not be true
-    // when there are multiple subscriptions per sim and probably for other reasons.
-    public int getSubId(int phoneId) {
-        int[] subIds = getSubIds(phoneId);
-        if (subIds == null || subIds.length == 0) {
-            return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-        }
-        return subIds[0];
-    }
-
-    /** Must be public for access from instrumentation tests. */
-    @VisibleForTesting
-    public List<SubscriptionInfo> getSubInfoUsingSlotIndexPrivileged(int slotIndex) {
-        if (DBG) logd("[getSubInfoUsingSlotIndexPrivileged]+ slotIndex:" + slotIndex);
-        if (slotIndex == SubscriptionManager.DEFAULT_SIM_SLOT_INDEX) {
-            slotIndex = getSlotIndex(getDefaultSubId());
-        }
-        if (!SubscriptionManager.isValidSlotIndex(slotIndex)) {
-            if (DBG) logd("[getSubInfoUsingSlotIndexPrivileged]- invalid slotIndex");
-            return null;
-        }
-
-        Cursor cursor = mContext.getContentResolver().query(SubscriptionManager.CONTENT_URI,
-                null, SubscriptionManager.SIM_SLOT_INDEX + "=?",
-                new String[]{String.valueOf(slotIndex)}, null);
-        ArrayList<SubscriptionInfo> subList = null;
-        try {
-            if (cursor != null) {
-                while (cursor.moveToNext()) {
-                    SubscriptionInfo subInfo = getSubInfoRecord(cursor);
-                    if (subInfo != null) {
-                        if (subList == null) {
-                            subList = new ArrayList<SubscriptionInfo>();
-                        }
-                        subList.add(subInfo);
-                    }
-                }
-            }
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
-        }
-        if (DBG) logd("[getSubInfoUsingSlotIndex]- null info return");
-
-        return subList;
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private void validateSubId(int subId) {
-        if (DBG) logd("validateSubId subId: " + subId);
-        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
-            throw new IllegalArgumentException("Invalid sub id passed as parameter");
-        } else if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
-            throw new IllegalArgumentException("Default sub id passed as parameter");
-        }
-    }
-
-    private synchronized ArrayList<Integer> getActiveSubIdArrayList() {
-        // Clone the sub id list so it can't change out from under us while iterating
-        List<Entry<Integer, ArrayList<Integer>>> simInfoList =
-                new ArrayList<>(mSlotIndexToSubIds.entrySet());
-
-        // Put the set of sub ids in slot index order
-        Collections.sort(simInfoList, (x, y) -> x.getKey().compareTo(y.getKey()));
-
-        // Collect the sub ids for each slot in turn
-        ArrayList<Integer> allSubs = new ArrayList<>();
-        for (Entry<Integer, ArrayList<Integer>> slot : simInfoList) {
-            allSubs.addAll(slot.getValue());
-        }
-        return allSubs;
-    }
-
-    private boolean isSubscriptionVisible(int subId) {
-        synchronized (mSubInfoListLock) {
-            for (SubscriptionInfo info : mCacheOpportunisticSubInfoList) {
-                if (info.getSubscriptionId() == subId) {
-                    // If group UUID is null, it's stand alone opportunistic profile. So it's
-                    // visible. Otherwise, it's bundled opportunistic profile, and is not visible.
-                    return info.getGroupUuid() == null;
-                }
-            }
-        }
-
-        return true;
-    }
-
-    /**
-     * @return the list of subId's that are active, is never null but the length maybe 0.
-     */
-    @Override
-    public int[] getActiveSubIdList(boolean visibleOnly) {
-        enforceReadPrivilegedPhoneState("getActiveSubIdList");
-
-        final long token = Binder.clearCallingIdentity();
-        try {
-            List<Integer> allSubs = getActiveSubIdArrayList();
-
-            if (visibleOnly) {
-                // Grouped opportunistic subscriptions should be hidden.
-                allSubs = allSubs.stream().filter(subId -> isSubscriptionVisible(subId))
-                        .collect(Collectors.toList());
-            }
-
-            int[] subIdArr = new int[allSubs.size()];
-            int i = 0;
-            for (int sub : allSubs) {
-                subIdArr[i] = sub;
-                i++;
-            }
-
-            if (VDBG) {
-                logdl("[getActiveSubIdList] allSubs=" + allSubs + " subIdArr.length="
-                        + subIdArr.length);
-            }
-            return subIdArr;
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    @Override
-    public boolean isActiveSubId(int subId, String callingPackage, String callingFeatureId) {
-        if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subId, callingPackage,
-                callingFeatureId, "isActiveSubId")) {
-            throw new SecurityException("Requires READ_PHONE_STATE permission.");
-        }
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            return isActiveSubId(subId);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @Deprecated // This should be moved into isActiveSubId(int, String)
-    public boolean isActiveSubId(int subId) {
-        boolean retVal = SubscriptionManager.isValidSubscriptionId(subId)
-                && getActiveSubIdArrayList().contains(subId);
-
-        if (VDBG) logdl("[isActiveSubId]- " + retVal);
-        return retVal;
-    }
-
-    /**
-     * Store properties associated with SubscriptionInfo in database
-     * @param subId Subscription Id of Subscription
-     * @param propKey Column name in database associated with SubscriptionInfo
-     * @param propValue Value to store in DB for particular subId & column name
-     *
-     * @return number of rows updated.
-     * @hide
-     */
-    @Override
-    public int setSubscriptionProperty(int subId, String propKey, String propValue) {
-        enforceModifyPhoneState("setSubscriptionProperty");
-        final long token = Binder.clearCallingIdentity();
-
-        try {
-            validateSubId(subId);
-            ContentResolver resolver = mContext.getContentResolver();
-            int result = setSubscriptionPropertyIntoContentResolver(
-                    subId, propKey, propValue, resolver);
-            // Refresh the Cache of Active Subscription Info List
-            refreshCachedActiveSubscriptionInfoList();
-
-            return result;
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    private int setSubscriptionPropertyIntoContentResolver(
-            int subId, String propKey, String propValue, ContentResolver resolver) {
-        ContentValues value = new ContentValues();
-        boolean updateEntireGroup = GROUP_SHARING_PROPERTIES.contains(propKey);
-        switch (propKey) {
-            case SubscriptionManager.CB_EXTREME_THREAT_ALERT:
-            case SubscriptionManager.CB_SEVERE_THREAT_ALERT:
-            case SubscriptionManager.CB_AMBER_ALERT:
-            case SubscriptionManager.CB_EMERGENCY_ALERT:
-            case SubscriptionManager.CB_ALERT_SOUND_DURATION:
-            case SubscriptionManager.CB_ALERT_REMINDER_INTERVAL:
-            case SubscriptionManager.CB_ALERT_VIBRATE:
-            case SubscriptionManager.CB_ALERT_SPEECH:
-            case SubscriptionManager.CB_ETWS_TEST_ALERT:
-            case SubscriptionManager.CB_CHANNEL_50_ALERT:
-            case SubscriptionManager.CB_CMAS_TEST_ALERT:
-            case SubscriptionManager.CB_OPT_OUT_DIALOG:
-            case SubscriptionManager.ENHANCED_4G_MODE_ENABLED:
-            case SubscriptionManager.IS_OPPORTUNISTIC:
-            case SubscriptionManager.VT_IMS_ENABLED:
-            case SubscriptionManager.WFC_IMS_ENABLED:
-            case SubscriptionManager.WFC_IMS_MODE:
-            case SubscriptionManager.WFC_IMS_ROAMING_MODE:
-            case SubscriptionManager.WFC_IMS_ROAMING_ENABLED:
-            case SubscriptionManager.IMS_RCS_UCE_ENABLED:
-            case SubscriptionManager.CROSS_SIM_CALLING_ENABLED:
-            case SubscriptionManager.VOIMS_OPT_IN_STATUS:
-            case SubscriptionManager.NR_ADVANCED_CALLING_ENABLED:
-            case SubscriptionManager.USAGE_SETTING:
-            case SubscriptionManager.USER_HANDLE:
-            case SubscriptionManager.SATELLITE_ENABLED:
-                value.put(propKey, Integer.parseInt(propValue));
-                break;
-            case SubscriptionManager.ALLOWED_NETWORK_TYPES:
-            case SimInfo.COLUMN_PHONE_NUMBER_SOURCE_CARRIER:
-            case SimInfo.COLUMN_PHONE_NUMBER_SOURCE_IMS:
-                value.put(propKey, propValue);
-                break;
-            default:
-                if (DBG) logd("Invalid column name");
-                break;
-        }
-
-        return updateDatabase(value, subId, updateEntireGroup);
-    }
-
-    /**
-     * Get properties associated with SubscriptionInfo from database
-     *
-     * @param subId Subscription Id of Subscription
-     * @param propKey Column name in SubscriptionInfo database
-     * @return Value associated with subId and propKey column in database
-     */
-    @Override
-    public String getSubscriptionProperty(int subId, String propKey, String callingPackage,
-            String callingFeatureId) {
-        switch (propKey) {
-            case SubscriptionManager.GROUP_UUID:
-                if (mContext.checkCallingOrSelfPermission(
-                        Manifest.permission.READ_PRIVILEGED_PHONE_STATE) != PERMISSION_GRANTED) {
-                    EventLog.writeEvent(0x534e4554, "213457638", Binder.getCallingUid());
-                    return null;
-                }
-                break;
-            default:
-                if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subId,
-                        callingPackage, callingFeatureId, "getSubscriptionProperty")) {
-                    return null;
-                }
-        }
-
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            return getSubscriptionProperty(subId, propKey);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    /**
-     * Get properties associated with SubscriptionInfo from database. Note this is the version
-     * without permission check for telephony internal use only.
-     *
-     * @param subId Subscription Id of Subscription
-     * @param propKey Column name in SubscriptionInfo database
-     * @return Value associated with subId and propKey column in database
-     */
-    public String getSubscriptionProperty(int subId, String propKey) {
-        String resultValue = null;
-        try (Cursor cursor = mContext.getContentResolver().query(SubscriptionManager.CONTENT_URI,
-                new String[]{propKey},
-                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=?",
-                new String[]{subId + ""}, null)) {
-            if (cursor != null) {
-                if (cursor.moveToFirst()) {
-                    switch (propKey) {
-                        case SubscriptionManager.CB_EXTREME_THREAT_ALERT:
-                        case SubscriptionManager.CB_SEVERE_THREAT_ALERT:
-                        case SubscriptionManager.CB_AMBER_ALERT:
-                        case SubscriptionManager.CB_EMERGENCY_ALERT:
-                        case SubscriptionManager.CB_ALERT_SOUND_DURATION:
-                        case SubscriptionManager.CB_ALERT_REMINDER_INTERVAL:
-                        case SubscriptionManager.CB_ALERT_VIBRATE:
-                        case SubscriptionManager.CB_ALERT_SPEECH:
-                        case SubscriptionManager.CB_ETWS_TEST_ALERT:
-                        case SubscriptionManager.CB_CHANNEL_50_ALERT:
-                        case SubscriptionManager.CB_CMAS_TEST_ALERT:
-                        case SubscriptionManager.CB_OPT_OUT_DIALOG:
-                        case SubscriptionManager.ENHANCED_4G_MODE_ENABLED:
-                        case SubscriptionManager.VT_IMS_ENABLED:
-                        case SubscriptionManager.WFC_IMS_ENABLED:
-                        case SubscriptionManager.WFC_IMS_MODE:
-                        case SubscriptionManager.WFC_IMS_ROAMING_MODE:
-                        case SubscriptionManager.WFC_IMS_ROAMING_ENABLED:
-                        case SubscriptionManager.IMS_RCS_UCE_ENABLED:
-                        case SubscriptionManager.CROSS_SIM_CALLING_ENABLED:
-                        case SubscriptionManager.IS_OPPORTUNISTIC:
-                        case SubscriptionManager.GROUP_UUID:
-                        case SubscriptionManager.ENABLED_MOBILE_DATA_POLICIES:
-                        case SubscriptionManager.ALLOWED_NETWORK_TYPES:
-                        case SubscriptionManager.D2D_STATUS_SHARING:
-                        case SubscriptionManager.VOIMS_OPT_IN_STATUS:
-                        case SubscriptionManager.D2D_STATUS_SHARING_SELECTED_CONTACTS:
-                        case SubscriptionManager.NR_ADVANCED_CALLING_ENABLED:
-                        case SimInfo.COLUMN_PHONE_NUMBER_SOURCE_CARRIER:
-                        case SimInfo.COLUMN_PHONE_NUMBER_SOURCE_IMS:
-                        case SubscriptionManager.USAGE_SETTING:
-                        case SubscriptionManager.USER_HANDLE:
-                        case SubscriptionManager.SATELLITE_ENABLED:
-                            resultValue = cursor.getString(0);
-                            break;
-                        default:
-                            if(DBG) logd("Invalid column name");
-                            break;
-                    }
-                } else {
-                    if(DBG) logd("Valid row not present in db");
-                }
-            } else {
-                if(DBG) logd("Query failed");
-            }
-        }
-
-        if (DBG) logd("getSubscriptionProperty Query value = " + resultValue);
-        return resultValue;
-    }
-
-    private void printStackTrace(String msg) {
-        RuntimeException re = new RuntimeException();
-        logd("StackTrace - " + msg);
-        StackTraceElement[] st = re.getStackTrace();
-        boolean first = true;
-        for (StackTraceElement ste : st) {
-            if (first) {
-                first = false;
-            } else {
-                logd(ste.toString());
-            }
-        }
-    }
-
-    @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP,
-                "Requires DUMP");
-        final long token = Binder.clearCallingIdentity();
-        try {
-            pw.println("SubscriptionController:");
-            pw.println(" mLastISubServiceRegTime=" + mLastISubServiceRegTime);
-            pw.println(" defaultSubId=" + getDefaultSubId());
-            pw.println(" defaultDataSubId=" + getDefaultDataSubId());
-            pw.println(" defaultVoiceSubId=" + getDefaultVoiceSubId());
-            pw.println(" defaultSmsSubId=" + getDefaultSmsSubId());
-            pw.println(" defaultVoicePhoneId=" + SubscriptionManager.getDefaultVoicePhoneId());
-            pw.flush();
-
-            for (Entry<Integer, ArrayList<Integer>> entry : mSlotIndexToSubIds.entrySet()) {
-                pw.println(" sSlotIndexToSubId[" + entry.getKey() + "]: subIds=" + entry);
-            }
-            pw.flush();
-            pw.println("++++++++++++++++++++++++++++++++");
-
-            List<SubscriptionInfo> sirl = getActiveSubscriptionInfoList(
-                    mContext.getOpPackageName(), mContext.getAttributionTag());
-            if (sirl != null) {
-                pw.println(" ActiveSubInfoList:");
-                for (SubscriptionInfo entry : sirl) {
-                    pw.println("  " + entry.toString());
-                }
-            } else {
-                pw.println(" ActiveSubInfoList: is null");
-            }
-            pw.flush();
-            pw.println("++++++++++++++++++++++++++++++++");
-
-            sirl = getAllSubInfoList(mContext.getOpPackageName(), mContext.getAttributionTag());
-            if (sirl != null) {
-                pw.println(" AllSubInfoList:");
-                for (SubscriptionInfo entry : sirl) {
-                    pw.println("  " + entry.toString());
-                }
-            } else {
-                pw.println(" AllSubInfoList: is null");
-            }
-            pw.flush();
-            pw.println("++++++++++++++++++++++++++++++++");
-
-            mLocalLog.dump(fd, pw, args);
-            pw.flush();
-            pw.println("++++++++++++++++++++++++++++++++");
-            pw.flush();
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    /**
-     * Migrating Ims settings from global setting to subscription DB, if not already done.
-     */
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    public void migrateImsSettings() {
-        migrateImsSettingHelper(
-                Settings.Global.ENHANCED_4G_MODE_ENABLED,
-                SubscriptionManager.ENHANCED_4G_MODE_ENABLED);
-        migrateImsSettingHelper(
-                Settings.Global.VT_IMS_ENABLED,
-                SubscriptionManager.VT_IMS_ENABLED);
-        migrateImsSettingHelper(
-                Settings.Global.WFC_IMS_ENABLED,
-                SubscriptionManager.WFC_IMS_ENABLED);
-        migrateImsSettingHelper(
-                Settings.Global.WFC_IMS_MODE,
-                SubscriptionManager.WFC_IMS_MODE);
-        migrateImsSettingHelper(
-                Settings.Global.WFC_IMS_ROAMING_MODE,
-                SubscriptionManager.WFC_IMS_ROAMING_MODE);
-        migrateImsSettingHelper(
-                Settings.Global.WFC_IMS_ROAMING_ENABLED,
-                SubscriptionManager.WFC_IMS_ROAMING_ENABLED);
-    }
-
-    private void migrateImsSettingHelper(String settingGlobal, String subscriptionProperty) {
-        ContentResolver resolver = mContext.getContentResolver();
-        int defaultSubId = getDefaultVoiceSubId();
-        if (defaultSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
-            return;
-        }
-        try {
-            int prevSetting = Settings.Global.getInt(resolver, settingGlobal);
-
-            if (prevSetting != DEPRECATED_SETTING) {
-                // Write previous setting into Subscription DB.
-                setSubscriptionPropertyIntoContentResolver(defaultSubId, subscriptionProperty,
-                        Integer.toString(prevSetting), resolver);
-                // Write global setting value with DEPRECATED_SETTING making sure
-                // migration only happen once.
-                Settings.Global.putInt(resolver, settingGlobal, DEPRECATED_SETTING);
-            }
-        } catch (Settings.SettingNotFoundException e) {
-        }
-    }
-
-    /**
-     * Set whether a subscription is opportunistic.
-     *
-     * Throws SecurityException if doesn't have required permission.
-     *
-     * @param opportunistic whether it’s opportunistic subscription.
-     * @param subId the unique SubscriptionInfo index in database
-     * @param callingPackage The package making the IPC.
-     * @return the number of records updated
-     */
-    @Override
-    public int setOpportunistic(boolean opportunistic, int subId, String callingPackage) {
-        try {
-            TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
-                    mContext, subId, callingPackage);
-        } catch (SecurityException e) {
-            // The subscription may be inactive eSIM profile. If so, check the access rule in
-            // database.
-            enforceCarrierPrivilegeOnInactiveSub(subId, callingPackage,
-                    "Caller requires permission on sub " + subId);
-        }
-
-        long token = Binder.clearCallingIdentity();
-        try {
-            int ret = setSubscriptionProperty(subId, SubscriptionManager.IS_OPPORTUNISTIC,
-                    String.valueOf(opportunistic ? 1 : 0));
-            if (ret != 0) notifySubscriptionInfoChanged();
-            return ret;
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    /**
-     * Get subscription info from database, and check whether caller has carrier privilege
-     * permission with it. If checking fails, throws SecurityException.
-     */
-    private void enforceCarrierPrivilegeOnInactiveSub(int subId, String callingPackage,
-            String message) {
-        mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
-
-        SubscriptionManager subManager = (SubscriptionManager)
-                mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
-
-        List<SubscriptionInfo> subInfo;
-        long token = Binder.clearCallingIdentity();
-        try {
-            subInfo = getSubInfo(
-                    SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + subId, null);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-
-        try {
-            if (!isActiveSubId(subId) && subInfo != null && subInfo.size() == 1
-                    && subManager.canManageSubscription(subInfo.get(0), callingPackage)) {
-                return;
-            }
-            throw new SecurityException(message);
-        } catch (IllegalArgumentException e) {
-            // canManageSubscription will throw IllegalArgumentException if sub is not embedded
-            // or package name is unknown. In this case, we also see it as permission check failure
-            // and throw a SecurityException.
-            throw new SecurityException(message);
-        }
-    }
-
-    @Override
-    public void setPreferredDataSubscriptionId(int subId, boolean needValidation,
-            ISetOpportunisticDataCallback callback) {
-        enforceModifyPhoneState("setPreferredDataSubscriptionId");
-        final long token = Binder.clearCallingIdentity();
-
-        try {
-            PhoneSwitcher phoneSwitcher = PhoneSwitcher.getInstance();
-            if (phoneSwitcher == null) {
-                logd("Set preferred data sub: phoneSwitcher is null.");
-                AnomalyReporter.reportAnomaly(
-                        UUID.fromString("a73fe57f-4178-4bc3-a7ae-9d7354939274"),
-                        "Set preferred data sub: phoneSwitcher is null.");
-                if (callback != null) {
-                    try {
-                        callback.onComplete(SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION);
-                    } catch (RemoteException exception) {
-                        logd("RemoteException " + exception);
-                    }
-                }
-                return;
-            }
-
-            phoneSwitcher.trySetOpportunisticDataSubscription(subId, needValidation, callback);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    @Override
-    public int getPreferredDataSubscriptionId() {
-        enforceReadPrivilegedPhoneState("getPreferredDataSubscriptionId");
-        final long token = Binder.clearCallingIdentity();
-
-        try {
-            PhoneSwitcher phoneSwitcher = PhoneSwitcher.getInstance();
-            if (phoneSwitcher == null) {
-                AnomalyReporter.reportAnomaly(
-                        UUID.fromString("e72747ab-d0aa-4b0e-9dd5-cb99365c6d58"),
-                        "Get preferred data sub: phoneSwitcher is null.");
-                return SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
-            }
-
-            return phoneSwitcher.getAutoSelectedDataSubId();
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    @Override
-    public List<SubscriptionInfo> getOpportunisticSubscriptions(String callingPackage,
-            String callingFeatureId) {
-        return getSubscriptionInfoListFromCacheHelper(callingPackage, callingFeatureId,
-                makeCacheListCopyWithLock(mCacheOpportunisticSubInfoList));
-    }
-
-    /**
-     * Inform SubscriptionManager that subscriptions in the list are bundled
-     * as a group. Typically it's a primary subscription and an opportunistic
-     * subscription. It should only affect multi-SIM scenarios where primary
-     * and opportunistic subscriptions can be activated together.
-     * Being in the same group means they might be activated or deactivated
-     * together, some of them may be invisible to the users, etc.
-     *
-     * Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE}
-     * permission or had carrier privilege permission on the subscriptions:
-     * {@link TelephonyManager#hasCarrierPrivileges(int)} or
-     * {@link SubscriptionManager#canManageSubscription(SubscriptionInfo)}
-     *
-     * @throws SecurityException if the caller doesn't meet the requirements
-     *             outlined above.
-     * @throws IllegalArgumentException if the some subscriptions in the list doesn't exist.
-     *
-     * @param subIdList list of subId that will be in the same group
-     * @return groupUUID a UUID assigned to the subscription group. It returns
-     * null if fails.
-     *
-     */
-    @Override
-    public ParcelUuid createSubscriptionGroup(int[] subIdList, String callingPackage) {
-        if (subIdList == null || subIdList.length == 0) {
-            throw new IllegalArgumentException("Invalid subIdList " + Arrays.toString(subIdList));
-        }
-
-        // Makes sure calling package matches caller UID.
-        mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
-        // If it doesn't have modify phone state permission, or carrier privilege permission,
-        // a SecurityException will be thrown.
-        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-                != PERMISSION_GRANTED && !checkCarrierPrivilegeOnSubList(
-                        subIdList, callingPackage)) {
-            throw new SecurityException("CreateSubscriptionGroup needs MODIFY_PHONE_STATE or"
-                    + " carrier privilege permission on all specified subscriptions");
-        }
-
-        long identity = Binder.clearCallingIdentity();
-
-        try {
-            // Generate a UUID.
-            ParcelUuid groupUUID = new ParcelUuid(UUID.randomUUID());
-
-            ContentValues value = new ContentValues();
-            value.put(SubscriptionManager.GROUP_UUID, groupUUID.toString());
-            value.put(SubscriptionManager.GROUP_OWNER, callingPackage);
-            int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
-                    value, getSelectionForSubIdList(subIdList), null);
-
-            if (DBG) logdl("createSubscriptionGroup update DB result: " + result);
-
-            refreshCachedActiveSubscriptionInfoList();
-
-            notifySubscriptionInfoChanged();
-
-            MultiSimSettingController.getInstance().notifySubscriptionGroupChanged(groupUUID);
-
-            return groupUUID;
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    private String getOwnerPackageOfSubGroup(ParcelUuid groupUuid) {
-        if (groupUuid == null) return null;
-
-        List<SubscriptionInfo> infoList = getSubInfo(SubscriptionManager.GROUP_UUID
-                + "=\'" + groupUuid.toString() + "\'", null);
-
-        return ArrayUtils.isEmpty(infoList) ? null : infoList.get(0).getGroupOwner();
-    }
-
-    /**
-     * @param groupUuid a UUID assigned to the subscription group.
-     * @param callingPackage the package making the IPC.
-     * @return if callingPackage has carrier privilege on sublist.
-     *
-     */
-    public boolean canPackageManageGroup(ParcelUuid groupUuid, String callingPackage) {
-        if (groupUuid == null) {
-            throw new IllegalArgumentException("Invalid groupUuid");
-        }
-
-        if (TextUtils.isEmpty(callingPackage)) {
-            throw new IllegalArgumentException("Empty callingPackage");
-        }
-
-        List<SubscriptionInfo> infoList;
-
-        // Getting all subscriptions in the group.
-        long identity = Binder.clearCallingIdentity();
-        try {
-            infoList = getSubInfo(SubscriptionManager.GROUP_UUID
-                    + "=\'" + groupUuid.toString() + "\'", null);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-
-        // If the group does not exist, then by default the UUID is up for grabs so no need to
-        // restrict management of a group (that someone may be attempting to create).
-        if (ArrayUtils.isEmpty(infoList)) {
-            return true;
-        }
-
-        // If the calling package is the group owner, skip carrier permission check and return
-        // true as it was done before.
-        if (callingPackage.equals(infoList.get(0).getGroupOwner())) return true;
-
-        // Check carrier privilege for all subscriptions in the group.
-        int[] subIdArray = infoList.stream().mapToInt(info -> info.getSubscriptionId())
-                .toArray();
-        return (checkCarrierPrivilegeOnSubList(subIdArray, callingPackage));
-    }
-
-    private int updateGroupOwner(ParcelUuid groupUuid, String groupOwner) {
-        // If the existing group owner is different from current caller, make caller the new
-        // owner of all subscriptions in group.
-        // This is for use-case of:
-        // 1) Both package1 and package2 has permission (MODIFY_PHONE_STATE or carrier
-        // privilege permission) of all related subscriptions.
-        // 2) Package 1 created a group.
-        // 3) Package 2 wants to add a subscription into it.
-        // Step 3 should be granted as all operations are permission based. Which means as
-        // long as the package passes the permission check, it can modify the subscription
-        // and the group. And package 2 becomes the new group owner as it's the last to pass
-        // permission checks on all members.
-        ContentValues value = new ContentValues(1);
-        value.put(SubscriptionManager.GROUP_OWNER, groupOwner);
-        return mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
-                value, SubscriptionManager.GROUP_UUID + "=\"" + groupUuid + "\"", null);
-    }
-
-    @Override
-    public void addSubscriptionsIntoGroup(int[] subIdList, ParcelUuid groupUuid,
-            String callingPackage) {
-        if (subIdList == null || subIdList.length == 0) {
-            throw new IllegalArgumentException("Invalid subId list");
-        }
-
-        if (groupUuid == null || groupUuid.equals(INVALID_GROUP_UUID)) {
-            throw new IllegalArgumentException("Invalid groupUuid");
-        }
-
-        // Makes sure calling package matches caller UID.
-        mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
-        // If it doesn't have modify phone state permission, or carrier privilege permission,
-        // a SecurityException will be thrown.
-        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-                != PERMISSION_GRANTED && !(checkCarrierPrivilegeOnSubList(subIdList, callingPackage)
-                && canPackageManageGroup(groupUuid, callingPackage))) {
-            throw new SecurityException("Requires MODIFY_PHONE_STATE or carrier privilege"
-                    + " permissions on subscriptions and the group.");
-        }
-
-        long identity = Binder.clearCallingIdentity();
-
-        try {
-            if (DBG) {
-                logdl("addSubscriptionsIntoGroup sub list "
-                        + Arrays.toString(subIdList) + " into group " + groupUuid);
-            }
-
-            ContentValues value = new ContentValues();
-            value.put(SubscriptionManager.GROUP_UUID, groupUuid.toString());
-            int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
-                    value, getSelectionForSubIdList(subIdList), null);
-
-            if (DBG) logdl("addSubscriptionsIntoGroup update DB result: " + result);
-
-            if (result > 0) {
-                updateGroupOwner(groupUuid, callingPackage);
-                refreshCachedActiveSubscriptionInfoList();
-                notifySubscriptionInfoChanged();
-                MultiSimSettingController.getInstance().notifySubscriptionGroupChanged(groupUuid);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    /**
-     * Remove a list of subscriptions from their subscription group.
-     * See {@link SubscriptionManager#createSubscriptionGroup(List<Integer>)} for more details.
-     *
-     * Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE}
-     * permission or had carrier privilege permission on the subscriptions:
-     * {@link TelephonyManager#hasCarrierPrivileges()} or
-     * {@link SubscriptionManager#canManageSubscription(SubscriptionInfo)}
-     *
-     * @throws SecurityException if the caller doesn't meet the requirements
-     *             outlined above.
-     * @throws IllegalArgumentException if the some subscriptions in the list doesn't belong
-     *             the specified group.
-     *
-     * @param subIdList list of subId that need removing from their groups.
-     *
-     */
-    public void removeSubscriptionsFromGroup(int[] subIdList, ParcelUuid groupUuid,
-            String callingPackage) {
-        if (subIdList == null || subIdList.length == 0) {
-            return;
-        }
-
-        // Makes sure calling package matches caller UID.
-        mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
-        // If it doesn't have modify phone state permission, or carrier privilege permission,
-        // a SecurityException will be thrown. If it's due to invalid parameter or internal state,
-        // it will return null.
-        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-                != PERMISSION_GRANTED && !(checkCarrierPrivilegeOnSubList(subIdList, callingPackage)
-                && canPackageManageGroup(groupUuid, callingPackage))) {
-            throw new SecurityException("removeSubscriptionsFromGroup needs MODIFY_PHONE_STATE or"
-                    + " carrier privilege permission on all specified subscriptions");
-        }
-
-        long identity = Binder.clearCallingIdentity();
-
-        try {
-            List<SubscriptionInfo> subInfoList = getSubInfo(getSelectionForSubIdList(subIdList),
-                    null);
-            for (SubscriptionInfo info : subInfoList) {
-                if (!groupUuid.equals(info.getGroupUuid())) {
-                    throw new IllegalArgumentException("Subscription " + info.getSubscriptionId()
-                        + " doesn't belong to group " + groupUuid);
-                }
-            }
-            ContentValues value = new ContentValues();
-            value.put(SubscriptionManager.GROUP_UUID, (String) null);
-            value.put(SubscriptionManager.GROUP_OWNER, (String) null);
-            int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
-                    value, getSelectionForSubIdList(subIdList), null);
-
-            if (DBG) logdl("removeSubscriptionsFromGroup update DB result: " + result);
-
-            if (result > 0) {
-                updateGroupOwner(groupUuid, callingPackage);
-                refreshCachedActiveSubscriptionInfoList();
-                notifySubscriptionInfoChanged();
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    /**
-     *  Helper function to check if the caller has carrier privilege permissions on a list of subId.
-     *  The check can either be processed against access rules on currently active SIM cards, or
-     *  the access rules we keep in our database for currently inactive eSIMs.
-     *
-     * @throws IllegalArgumentException if the some subId is invalid or doesn't exist.
-     *
-     *  @return true if checking passes on all subId, false otherwise.
-     */
-    private boolean checkCarrierPrivilegeOnSubList(int[] subIdList, String callingPackage) {
-        // Check carrier privilege permission on active subscriptions first.
-        // If it fails, they could be inactive. So keep them in a HashSet and later check
-        // access rules in our database.
-        Set<Integer> checkSubList = new HashSet<>();
-        for (int subId : subIdList) {
-            if (isActiveSubId(subId)) {
-                if (!mTelephonyManager.hasCarrierPrivileges(subId)) {
-                    return false;
-                }
-            } else {
-                checkSubList.add(subId);
-            }
-        }
-
-        if (checkSubList.isEmpty()) {
-            return true;
-        }
-
-        long identity = Binder.clearCallingIdentity();
-
-        try {
-            // Check access rules for each sub info.
-            SubscriptionManager subscriptionManager = (SubscriptionManager)
-                    mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
-            List<SubscriptionInfo> subInfoList = getSubInfo(
-                    getSelectionForSubIdList(subIdList), null);
-
-            // Didn't find all the subscriptions specified in subIdList.
-            if (subInfoList == null || subInfoList.size() != subIdList.length) {
-                throw new IllegalArgumentException("Invalid subInfoList.");
-            }
-
-            for (SubscriptionInfo subInfo : subInfoList) {
-                if (checkSubList.contains(subInfo.getSubscriptionId())) {
-                    if (subInfo.isEmbedded() && subscriptionManager.canManageSubscription(
-                            subInfo, callingPackage)) {
-                        checkSubList.remove(subInfo.getSubscriptionId());
-                    } else {
-                        return false;
-                    }
-                }
-            }
-
-            return checkSubList.isEmpty();
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    /**
-     * Helper function to create selection argument of a list of subId.
-     * The result should be: "in (subId1, subId2, ...)".
-     */
-    public static String getSelectionForSubIdList(int[] subId) {
-        StringBuilder selection = new StringBuilder();
-        selection.append(SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID);
-        selection.append(" IN (");
-        for (int i = 0; i < subId.length - 1; i++) {
-            selection.append(subId[i] + ", ");
-        }
-        selection.append(subId[subId.length - 1]);
-        selection.append(")");
-
-        return selection.toString();
-    }
-
-    /**
-     * Helper function to create selection argument of a list of subId.
-     * The result should be: "in (iccId1, iccId2, ...)".
-     */
-    private String getSelectionForIccIdList(String[] iccIds) {
-        StringBuilder selection = new StringBuilder();
-        selection.append(SubscriptionManager.ICC_ID);
-        selection.append(" IN (");
-        for (int i = 0; i < iccIds.length - 1; i++) {
-            selection.append("'" + iccIds[i] + "', ");
-        }
-        selection.append("'" + iccIds[iccIds.length - 1] + "'");
-        selection.append(")");
-
-        return selection.toString();
-    }
-
-    /**
-     * Get subscriptionInfo list of subscriptions that are in the same group of given subId.
-     * See {@link #createSubscriptionGroup(int[], String)} for more details.
-     *
-     * Caller must have {@link android.Manifest.permission#READ_PHONE_STATE}
-     * or carrier privilege permission on the subscription.
-     * {@link TelephonyManager#hasCarrierPrivileges(int)}
-     *
-     * <p>Starting with API level 33, the caller needs READ_PHONE_STATE and access to device
-     * identifiers to get the list of subscriptions associated with a group UUID.
-     * This method can be invoked if one of the following requirements is met:
-     * <ul>
-     *     <li>If the app has carrier privilege permission.
-     *     {@link TelephonyManager#hasCarrierPrivileges()}
-     *     <li>If the app has {@link android.Manifest.permission#READ_PHONE_STATE} and
-     *     access to device identifiers.
-     * </ul>
-     *
-     * @throws SecurityException if the caller doesn't meet the requirements
-     *             outlined above.
-     *
-     * @param groupUuid of which list of subInfo will be returned.
-     * @return list of subscriptionInfo that belong to the same group, including the given
-     * subscription itself. It will return an empty list if no subscription belongs to the group.
-     *
-     */
-    @Override
-    public List<SubscriptionInfo> getSubscriptionsInGroup(ParcelUuid groupUuid,
-            String callingPackage, String callingFeatureId) {
-        long identity = Binder.clearCallingIdentity();
-        List<SubscriptionInfo> subInfoList;
-
-        try {
-            // need to bypass removing identifier check because that will remove the subList without
-            // group id.
-            subInfoList = getAllSubInfoList(mContext.getOpPackageName(),
-                    mContext.getAttributionTag(), true);
-            if (groupUuid == null || subInfoList == null || subInfoList.isEmpty()) {
-                return new ArrayList<>();
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-
-        // If the calling app neither has carrier privileges nor READ_PHONE_STATE and access to
-        // device identifiers, it will return an empty list.
-        if (CompatChanges.isChangeEnabled(
-                REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID, Binder.getCallingUid())) {
-            try {
-                if (!TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mContext,
-                        callingPackage, callingFeatureId, "getSubscriptionsInGroup")) {
-                    EventLog.writeEvent(0x534e4554, "213902861", Binder.getCallingUid());
-                    return new ArrayList<>();
-                }
-            } catch (SecurityException e) {
-                EventLog.writeEvent(0x534e4554, "213902861", Binder.getCallingUid());
-                return new ArrayList<>();
-            }
-        }
-        return subInfoList.stream().filter(info -> {
-            if (!groupUuid.equals(info.getGroupUuid())) return false;
-            int subId = info.getSubscriptionId();
-            return TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subId,
-                    callingPackage, callingFeatureId, "getSubscriptionsInGroup")
-                    || info.canManageSubscription(mContext, callingPackage);
-        }).map(subscriptionInfo -> conditionallyRemoveIdentifiers(subscriptionInfo,
-                callingPackage, callingFeatureId, "getSubscriptionsInGroup"))
-        .collect(Collectors.toList());
-
-    }
-
-    /**
-     * Check if the passed in phoneId has a sub that belongs to the same group as the sub
-     * corresponding to the passed in iccid.
-     * @param phoneId phone id to check
-     * @param iccid ICCID to check
-     * @return true if sub/group is the same, false otherwise
-     */
-    public boolean checkPhoneIdAndIccIdMatch(int phoneId, String iccid) {
-        int subId = getSubId(phoneId);
-        if (!SubscriptionManager.isUsableSubIdValue(subId)) return false;
-        ParcelUuid groupUuid = getGroupUuid(subId);
-        List<SubscriptionInfo> subInfoList;
-        if (groupUuid != null) {
-            subInfoList = getSubInfo(SubscriptionManager.GROUP_UUID
-                    + "=\'" + groupUuid.toString() + "\'", null);
-        } else {
-            subInfoList = getSubInfo(SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID
-                    + "=" + subId, null);
-        }
-        return subInfoList != null && subInfoList.stream().anyMatch(
-                subInfo -> IccUtils.stripTrailingFs(subInfo.getIccId()).equals(
-                IccUtils.stripTrailingFs(iccid)));
-    }
-
-    public ParcelUuid getGroupUuid(int subId) {
-        ParcelUuid groupUuid;
-        List<SubscriptionInfo> subInfo = getSubInfo(SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID
-                        + "=" + subId, null);
-        if (subInfo == null || subInfo.size() == 0) {
-            groupUuid = null;
-        } else {
-            groupUuid = subInfo.get(0).getGroupUuid();
-        }
-
-        return groupUuid;
-    }
-
-
-    /**
-     * Enable/Disable a subscription
-     * @param enable true if enabling, false if disabling
-     * @param subId the unique SubInfoRecord index in database
-     *
-     * @return true if success, false if fails or the further action is
-     * needed hence it's redirected to Euicc.
-     */
-    public boolean setSubscriptionEnabled(boolean enable, int subId) {
-        enforceModifyPhoneState("setSubscriptionEnabled");
-
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            logd("setSubscriptionEnabled" + (enable ? " enable " : " disable ")
-                    + " subId " + subId);
-
-            // Error checking.
-            if (!SubscriptionManager.isUsableSubscriptionId(subId)) {
-                throw new IllegalArgumentException(
-                        "setSubscriptionEnabled not usable subId " + subId);
-            }
-
-            // Nothing to do if it's already active or inactive.
-            if (enable == isActiveSubscriptionId(subId)) return true;
-
-            SubscriptionInfo info = SubscriptionController.getInstance()
-                    .getAllSubInfoList(mContext.getOpPackageName(), mContext.getAttributionTag())
-                    .stream()
-                    .filter(subInfo -> subInfo.getSubscriptionId() == subId)
-                    .findFirst()
-                    .get();
-
-            if (info == null) {
-                logd("setSubscriptionEnabled subId " + subId + " doesn't exist.");
-                return false;
-            }
-
-            // TODO: make sure after slot mapping, we enable the uicc applications for the
-            // subscription we are enabling.
-            if (info.isEmbedded()) {
-                return enableEmbeddedSubscription(info, enable);
-            } else {
-                return enablePhysicalSubscription(info, enable);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    private boolean enableEmbeddedSubscription(SubscriptionInfo info, boolean enable) {
-        // We need to send intents to Euicc for operations:
-
-        // 1) In single SIM mode, turning on a eSIM subscription while pSIM is the active slot.
-        //    Euicc will ask user to switch to DSDS if supported or to confirm SIM slot
-        //    switching.
-        // 2) In DSDS mode, turning on / off an eSIM profile. Euicc can ask user whether
-        //    to turn on DSDS, or whether to switch from current active eSIM profile to it, or
-        //    to simply show a progress dialog.
-        // 3) In future, similar operations on triple SIM devices.
-        enableSubscriptionOverEuiccManager(info.getSubscriptionId(), enable,
-                SubscriptionManager.INVALID_SIM_SLOT_INDEX);
-        // returning false to indicate state is not changed. If changed, a subscriptionInfo
-        // change will be filed separately.
-        return false;
-
-        // TODO: uncomment or clean up if we decide whether to support standalone CBRS for Q.
-        // subId = enable ? subId : SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-        // updateEnabledSubscriptionGlobalSetting(subId, physicalSlotIndex);
-    }
-
-    private boolean enablePhysicalSubscription(SubscriptionInfo info, boolean enable) {
-        if (info == null || !SubscriptionManager.isValidSubscriptionId(info.getSubscriptionId())) {
-            return false;
-        }
-
-        int subId = info.getSubscriptionId();
-
-        UiccSlotInfo slotInfo = null;
-        int physicalSlotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
-        UiccSlotInfo[] slotsInfo = mTelephonyManager.getUiccSlotsInfo();
-        if (slotsInfo == null) return false;
-        for (int i = 0; i < slotsInfo.length; i++) {
-            UiccSlotInfo curSlotInfo = slotsInfo[i];
-            if (curSlotInfo.getCardStateInfo() == CARD_STATE_INFO_PRESENT) {
-                if (TextUtils.equals(IccUtils.stripTrailingFs(curSlotInfo.getCardId()),
-                        IccUtils.stripTrailingFs(info.getCardString()))) {
-                    slotInfo = curSlotInfo;
-                    physicalSlotIndex = i;
-                    break;
-                }
-            }
-        }
-
-        // Can't find the existing SIM.
-        if (slotInfo == null) {
-            loge("Can't find the existing SIM.");
-            return false;
-        }
-
-        // this for physical slot which has only one port
-        if (enable && !slotInfo.getPorts().stream().findFirst().get().isActive()) {
-            // We need to send intents to Euicc if we are turning on an inactive slot.
-            // Euicc will decide whether to ask user to switch to DSDS, or change SIM
-            // slot mapping.
-            EuiccManager euiccManager =
-                    (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE);
-            if (euiccManager != null && euiccManager.isEnabled()) {
-                enableSubscriptionOverEuiccManager(subId, enable, physicalSlotIndex);
-            } else {
-                // Enable / disable uicc applications.
-                if (!info.areUiccApplicationsEnabled()) setUiccApplicationsEnabled(enable, subId);
-                // If euiccManager is not enabled, we try to switch to DSDS if possible,
-                // or switch slot if not.
-                if (mTelephonyManager.isMultiSimSupported() == MULTISIM_ALLOWED) {
-                    PhoneConfigurationManager.getInstance().switchMultiSimConfig(
-                            mTelephonyManager.getSupportedModemCount());
-                } else {
-                    List<UiccSlotMapping> slotMapping = new ArrayList<>();
-                    // As this is single sim mode, set port index to 0 and logical slot index is 0
-                    slotMapping.add(new UiccSlotMapping(TelephonyManager.DEFAULT_PORT_INDEX,
-                            physicalSlotIndex, 0));
-                    UiccController.getInstance().switchSlots(slotMapping, null);
-                }
-            }
-            return true;
-        } else {
-            // Enable / disable uicc applications.
-            setUiccApplicationsEnabled(enable, subId);
-            return true;
-        }
-    }
-
-    private void enableSubscriptionOverEuiccManager(int subId, boolean enable,
-            int physicalSlotIndex) {
-        logdl("enableSubscriptionOverEuiccManager" + (enable ? " enable " : " disable ")
-                + "subId " + subId + " on slotIndex " + physicalSlotIndex);
-        Intent intent = new Intent(EuiccManager.ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        intent.putExtra(EuiccManager.EXTRA_SUBSCRIPTION_ID, subId);
-        intent.putExtra(EuiccManager.EXTRA_ENABLE_SUBSCRIPTION, enable);
-        if (physicalSlotIndex != SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
-            intent.putExtra(EuiccManager.EXTRA_PHYSICAL_SLOT_ID, physicalSlotIndex);
-        }
-        mContext.startActivity(intent);
-    }
-
-    private void updateEnabledSubscriptionGlobalSetting(int subId, int physicalSlotIndex) {
-        // Write the value which subscription is enabled into global setting.
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.ENABLED_SUBSCRIPTION_FOR_SLOT + physicalSlotIndex, subId);
-    }
-
-    private void updateModemStackEnabledGlobalSetting(boolean enabled, int physicalSlotIndex) {
-        // Write the whether a modem stack is disabled into global setting.
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.MODEM_STACK_ENABLED_FOR_SLOT
-                        + physicalSlotIndex, enabled ? 1 : 0);
-    }
-
-    private int getPhysicalSlotIndexFromLogicalSlotIndex(int logicalSlotIndex) {
-        int physicalSlotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
-        UiccSlotInfo[] slotInfos = mTelephonyManager.getUiccSlotsInfo();
-        for (int i = 0; i < slotInfos.length; i++) {
-            for (UiccPortInfo portInfo : slotInfos[i].getPorts()) {
-                if (portInfo.getLogicalSlotIndex() == logicalSlotIndex) {
-                    physicalSlotIndex = i;
-                    break;
-                }
-            }
-        }
-
-        return physicalSlotIndex;
-    }
-
-    @Override
-    public boolean isSubscriptionEnabled(int subId) {
-        // TODO: b/123314365 support multi-eSIM and removable eSIM.
-        enforceReadPrivilegedPhoneState("isSubscriptionEnabled");
-
-        long identity = Binder.clearCallingIdentity();
-        try {
-            // Error checking.
-            if (!SubscriptionManager.isUsableSubscriptionId(subId)) {
-                throw new IllegalArgumentException(
-                        "isSubscriptionEnabled not usable subId " + subId);
-            }
-
-            List<SubscriptionInfo> infoList = getSubInfo(
-                    SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + subId, null);
-            if (infoList == null || infoList.isEmpty()) {
-                // Subscription doesn't exist.
-                return false;
-            }
-
-            boolean isEmbedded = infoList.get(0).isEmbedded();
-
-            if (isEmbedded) {
-                return isActiveSubId(subId);
-            } else {
-                // For pSIM, we also need to check if modem is disabled or not.
-                return isActiveSubId(subId) && PhoneConfigurationManager.getInstance()
-                        .getPhoneStatus(PhoneFactory.getPhone(getPhoneId(subId)));
-            }
-
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    @Override
-    public int getEnabledSubscriptionId(int logicalSlotIndex) {
-        // TODO: b/123314365 support multi-eSIM and removable eSIM.
-        enforceReadPrivilegedPhoneState("getEnabledSubscriptionId");
-
-        long identity = Binder.clearCallingIdentity();
-        try {
-            if (!SubscriptionManager.isValidPhoneId(logicalSlotIndex)) {
-                throw new IllegalArgumentException(
-                        "getEnabledSubscriptionId with invalid logicalSlotIndex "
-                                + logicalSlotIndex);
-            }
-
-            // Getting and validating the physicalSlotIndex.
-            int physicalSlotIndex = getPhysicalSlotIndexFromLogicalSlotIndex(logicalSlotIndex);
-            if (physicalSlotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
-                return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-            }
-
-            // if modem stack is disabled, return INVALID_SUBSCRIPTION_ID without reading
-            // Settings.Global.ENABLED_SUBSCRIPTION_FOR_SLOT.
-            int modemStackEnabled = Settings.Global.getInt(mContext.getContentResolver(),
-                    Settings.Global.MODEM_STACK_ENABLED_FOR_SLOT + physicalSlotIndex, 1);
-            if (modemStackEnabled != 1) {
-                return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-            }
-
-            int subId;
-            try {
-                subId = Settings.Global.getInt(mContext.getContentResolver(),
-                        Settings.Global.ENABLED_SUBSCRIPTION_FOR_SLOT + physicalSlotIndex);
-            } catch (Settings.SettingNotFoundException e) {
-                // Value never set. Return whether it's currently active.
-                subId = getSubId(logicalSlotIndex);
-            }
-
-            return subId;
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    /**
-     * Helper function of getOpportunisticSubscriptions and getActiveSubscriptionInfoList.
-     * They are doing similar things except operating on different cache.
-     *
-     * NOTE: the cacheSubList passed in is a *copy* of mCacheActiveSubInfoList or
-     * mCacheOpportunisticSubInfoList, so mSubInfoListLock is not required to access it. Also, this
-     * method may modify cacheSubList depending on the permissions the caller has.
-     */
-    private List<SubscriptionInfo> getSubscriptionInfoListFromCacheHelper(
-            String callingPackage, String callingFeatureId, List<SubscriptionInfo> cacheSubList) {
-        boolean canReadPhoneState = false;
-        boolean canReadIdentifiers = false;
-        boolean canReadPhoneNumber = false;
-        try {
-            canReadPhoneState = TelephonyPermissions.checkReadPhoneState(mContext,
-                    SubscriptionManager.INVALID_SUBSCRIPTION_ID, Binder.getCallingPid(),
-                    Binder.getCallingUid(), callingPackage, callingFeatureId,
-                    "getSubscriptionInfoList");
-            // If the calling package has the READ_PHONE_STATE permission then check if the caller
-            // also has access to subscriber identifiers and the phone number to ensure that the ICC
-            // ID and any other unique identifiers are removed if the caller should not have access.
-            if (canReadPhoneState) {
-                canReadIdentifiers = hasSubscriberIdentifierAccess(
-                        SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage,
-                        callingFeatureId, "getSubscriptionInfoList", false);
-                canReadPhoneNumber = hasPhoneNumberAccess(
-                        SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage,
-                        callingFeatureId, "getSubscriptionInfoList");
-            }
-        } catch (SecurityException e) {
-            // If a SecurityException is thrown during the READ_PHONE_STATE check then the only way
-            // to access a subscription is to have carrier privileges for its subId; an app with
-            // carrier privileges for a subscription is also granted access to all identifiers so
-            // the identifier and phone number access checks are not required.
-        }
-
-        if (canReadIdentifiers && canReadPhoneNumber) {
-            return cacheSubList;
-        }
-        // Filter the list to only include subscriptions which the caller can manage.
-        for (int subIndex = cacheSubList.size() - 1; subIndex >= 0; subIndex--) {
-            SubscriptionInfo subscriptionInfo = cacheSubList.get(subIndex);
-
-            int subId = subscriptionInfo.getSubscriptionId();
-            boolean hasCarrierPrivileges = TelephonyPermissions.checkCarrierPrivilegeForSubId(
-                    mContext, subId);
-            // If the caller has carrier privileges then they are granted access to all
-            // identifiers for their subscription.
-            if (hasCarrierPrivileges) continue;
-
-            cacheSubList.remove(subIndex);
-            if (canReadPhoneState) {
-                // The caller does not have carrier privileges for this subId, filter the
-                // identifiers in the subscription based on the results of the initial
-                // permission checks.
-                cacheSubList.add(subIndex, conditionallyRemoveIdentifiers(
-                        subscriptionInfo, canReadIdentifiers, canReadPhoneNumber));
-            }
-        }
-        return cacheSubList;
-    }
-
-    /**
-     * Conditionally removes identifiers from the provided {@code subInfo} if the {@code
-     * callingPackage} does not meet the access requirements for identifiers and returns the
-     * potentially modified object..
-     *
-     * <p>If the caller does not meet the access requirements for identifiers a clone of the
-     * provided SubscriptionInfo is created and modified to avoid altering SubscriptionInfo objects
-     * in a cache.
-     */
-    private SubscriptionInfo conditionallyRemoveIdentifiers(SubscriptionInfo subInfo,
-            String callingPackage, String callingFeatureId, String message) {
-        SubscriptionInfo result = subInfo;
-        int subId = subInfo.getSubscriptionId();
-        boolean hasIdentifierAccess = hasSubscriberIdentifierAccess(subId, callingPackage,
-                callingFeatureId, message, true);
-        boolean hasPhoneNumberAccess = hasPhoneNumberAccess(subId, callingPackage, callingFeatureId,
-                message);
-        return conditionallyRemoveIdentifiers(subInfo, hasIdentifierAccess, hasPhoneNumberAccess);
-    }
-
-    /**
-     * Conditionally removes identifiers from the provided {@code subInfo} based on if the calling
-     * package {@code hasIdentifierAccess} and {@code hasPhoneNumberAccess} and returns the
-     * potentially modified object.
-     *
-     * <p>If the caller specifies the package does not have identifier or phone number access
-     * a clone of the provided SubscriptionInfo is created and modified to avoid altering
-     * SubscriptionInfo objects in a cache.
-     */
-    private SubscriptionInfo conditionallyRemoveIdentifiers(SubscriptionInfo subInfo,
-            boolean hasIdentifierAccess, boolean hasPhoneNumberAccess) {
-        if (hasIdentifierAccess && hasPhoneNumberAccess) {
-            return subInfo;
-        }
-        SubscriptionInfo.Builder result = new SubscriptionInfo.Builder(subInfo);
-        if (!hasIdentifierAccess) {
-            result.setIccId(null);
-            result.setCardString(null);
-            result.setGroupUuid(null);
-        }
-        if (!hasPhoneNumberAccess) {
-            result.setNumber(null);
-        }
-        return result.build();
-    }
-
-    private synchronized boolean addToSubIdList(int slotIndex, int subId, int subscriptionType) {
-        ArrayList<Integer> subIdsList = mSlotIndexToSubIds.getCopy(slotIndex);
-        if (subIdsList == null) {
-            subIdsList = new ArrayList<>();
-            mSlotIndexToSubIds.put(slotIndex, subIdsList);
-        }
-
-        // add the given subId unless it already exists
-        if (subIdsList.contains(subId)) {
-            logdl("slotIndex, subId combo already exists in the map. Not adding it again.");
-            return false;
-        }
-        if (isSubscriptionForRemoteSim(subscriptionType)) {
-            // For Remote SIM subscriptions, a slot can have multiple subscriptions.
-            mSlotIndexToSubIds.addToSubIdList(slotIndex, subId);
-        } else {
-            // for all other types of subscriptions, a slot can have only one subscription at a time
-            mSlotIndexToSubIds.clearSubIdList(slotIndex);
-            mSlotIndexToSubIds.addToSubIdList(slotIndex, subId);
-        }
-
-
-        // Remove the slot from sSlotIndexToSubIds if it has the same sub id with the added slot
-        for (Entry<Integer, ArrayList<Integer>> entry : mSlotIndexToSubIds.entrySet()) {
-            if (entry.getKey() != slotIndex && entry.getValue() != null
-                    && entry.getValue().contains(subId)) {
-                logdl("addToSubIdList - remove " + entry.getKey());
-                mSlotIndexToSubIds.remove(entry.getKey());
-            }
-        }
-
-        if (DBG) logdl("slotIndex, subId combo is added to the map.");
-        return true;
-    }
-
-    private boolean isSubscriptionForRemoteSim(int subscriptionType) {
-        return subscriptionType == SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM;
-    }
-
-    /**
-     * This is only for testing
-     * @hide
-     */
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    public Map<Integer, ArrayList<Integer>> getSlotIndexToSubIdsMap() {
-        return mSlotIndexToSubIds.getMap();
-    }
-
-    private void notifyOpportunisticSubscriptionInfoChanged() {
-        TelephonyRegistryManager trm =
-                (TelephonyRegistryManager)
-                        mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
-        if (DBG) logd("notifyOpptSubscriptionInfoChanged:");
-        trm.notifyOpportunisticSubscriptionInfoChanged();
-    }
-
-    private void refreshCachedOpportunisticSubscriptionInfoList() {
-        synchronized (mSubInfoListLock) {
-            List<SubscriptionInfo> subList = getSubInfo(
-                    SubscriptionManager.IS_OPPORTUNISTIC + "=1 AND ("
-                            + SubscriptionManager.SIM_SLOT_INDEX + ">=0 OR "
-                            + SubscriptionManager.IS_EMBEDDED + "=1)", null);
-            List<SubscriptionInfo> oldOpptCachedList = mCacheOpportunisticSubInfoList;
-
-            if (subList != null) {
-                subList.sort(SUBSCRIPTION_INFO_COMPARATOR);
-            } else {
-                subList = new ArrayList<>();
-            }
-
-            mCacheOpportunisticSubInfoList = subList;
-
-            for (int i = 0; i < mCacheOpportunisticSubInfoList.size(); i++) {
-                SubscriptionInfo info = mCacheOpportunisticSubInfoList.get(i);
-                if (shouldDisableSubGroup(info.getGroupUuid())) {
-                    SubscriptionInfo.Builder builder = new SubscriptionInfo.Builder(info);
-                    builder.setGroupDisabled(true);
-                    mCacheOpportunisticSubInfoList.set(i, builder.build());
-                }
-            }
-
-            if (DBG_CACHE) {
-                if (!mCacheOpportunisticSubInfoList.isEmpty()) {
-                    for (SubscriptionInfo si : mCacheOpportunisticSubInfoList) {
-                        logd("[refreshCachedOpportunisticSubscriptionInfoList] Setting Cached "
-                                + "info=" + si);
-                    }
-                } else {
-                    logdl("[refreshCachedOpportunisticSubscriptionInfoList]- no info return");
-                }
-            }
-
-            if (!oldOpptCachedList.equals(mCacheOpportunisticSubInfoList)) {
-                mOpptSubInfoListChangedDirtyBit.set(true);
-            }
-        }
-    }
-
-    private boolean shouldDisableSubGroup(ParcelUuid groupUuid) {
-        if (groupUuid == null) return false;
-
-        synchronized (mSubInfoListLock) {
-            for (SubscriptionInfo activeInfo : mCacheActiveSubInfoList) {
-                if (!activeInfo.isOpportunistic() && groupUuid.equals(activeInfo.getGroupUuid())) {
-                    return false;
-                }
-            }
-        }
-
-        return true;
-    }
-
-    /**
-     * Set enabled mobile data policies.
-     *
-     * @param subId Subscription index
-     * @param policies Mobile data policies in string format.
-     *                 See {@link TelephonyManager.MobileDataPolicy} for details.
-     * @return {@code true} if settings changed, otherwise {@code false}.
-     */
-    public boolean setEnabledMobileDataPolicies(int subId, @NonNull String policies) {
-        if (DBG) logd("[setEnabledMobileDataPolicies]+ policies:" + policies + " subId:" + subId);
-
-        validateSubId(subId);
-        ContentValues value = new ContentValues(1);
-        value.put(SubscriptionManager.ENABLED_MOBILE_DATA_POLICIES, policies);
-
-        boolean result = updateDatabase(value, subId, true) > 0;
-
-        if (result) {
-            // Refresh the Cache of Active Subscription Info List
-            refreshCachedActiveSubscriptionInfoList();
-            notifySubscriptionInfoChanged();
-        }
-
-        return result;
-    }
-
-    /**
-     * Get enabled mobile data policies.
-     *
-     * @param subId Subscription index
-     * @return Enabled mobile data policies joined by "," (ie. "1,2") or an empty string if no
-     * policies are enabled.
-     */
-    @NonNull
-    public String getEnabledMobileDataPolicies(int subId) {
-        return TelephonyUtils.emptyIfNull(getSubscriptionProperty(subId,
-                SubscriptionManager.ENABLED_MOBILE_DATA_POLICIES));
-    }
-
-    /**
-     * Get active data subscription id.
-     *
-     * @return Active data subscription id
-     *
-     * @hide
-     */
-    @Override
-    public int getActiveDataSubscriptionId() {
-        final long token = Binder.clearCallingIdentity();
-
-        try {
-            PhoneSwitcher phoneSwitcher = PhoneSwitcher.getInstance();
-            if (phoneSwitcher != null) {
-                int activeDataSubId = phoneSwitcher.getActiveDataSubId();
-                if (SubscriptionManager.isUsableSubscriptionId(activeDataSubId)) {
-                    return activeDataSubId;
-                }
-            }
-            // If phone switcher isn't ready, or active data sub id is not available, use default
-            // sub id from settings.
-            return getDefaultDataSubId();
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    /**
-     * Whether it's supported to disable / re-enable a subscription on a physical (non-euicc) SIM.
-     */
-    @Override
-    public boolean canDisablePhysicalSubscription() {
-        enforceReadPrivilegedPhoneState("canToggleUiccApplicationsEnablement");
-
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            Phone phone = PhoneFactory.getDefaultPhone();
-            return phone != null && phone.canDisablePhysicalSubscription();
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    /*
-     * Returns the phone number for the given {@code subId} and {@code source},
-     * or an empty string if not available.
-     */
-    @Override
-    public String getPhoneNumber(int subId, int source,
-            String callingPackage, String callingFeatureId) {
-        TelephonyPermissions.enforceAnyPermissionGrantedOrCarrierPrivileges(
-                mContext, subId, Binder.getCallingUid(), "getPhoneNumber",
-                READ_PHONE_NUMBERS, READ_PRIVILEGED_PHONE_STATE);
-
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            String number = getPhoneNumber(subId, source);
-            return number == null ? "" : number;
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    /*
-     * Returns the phone number for the given {@code subId} or an empty string if not available.
-     *
-     * <p>Built up on getPhoneNumber(int subId, int source) this API picks the 1st available
-     * source based on a priority order.
-     */
-    @Override
-    public String getPhoneNumberFromFirstAvailableSource(int subId,
-            String callingPackage, String callingFeatureId) {
-        TelephonyPermissions.enforceAnyPermissionGrantedOrCarrierPrivileges(
-                mContext, subId, Binder.getCallingUid(), "getPhoneNumberFromFirstAvailableSource",
-                READ_PHONE_NUMBERS, READ_PRIVILEGED_PHONE_STATE);
-
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            String numberFromCarrier = getPhoneNumber(
-                    subId, SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER);
-            if (!TextUtils.isEmpty(numberFromCarrier)) {
-                return numberFromCarrier;
-            }
-            String numberFromUicc = getPhoneNumber(
-                    subId, SubscriptionManager.PHONE_NUMBER_SOURCE_UICC);
-            if (!TextUtils.isEmpty(numberFromUicc)) {
-                return numberFromUicc;
-            }
-            String numberFromIms = getPhoneNumber(
-                    subId, SubscriptionManager.PHONE_NUMBER_SOURCE_IMS);
-            if (!TextUtils.isEmpty(numberFromIms)) {
-                return numberFromIms;
-            }
-            return "";
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    // Internal helper method for implementing getPhoneNumber() API.
-    @Nullable
-    private String getPhoneNumber(int subId, int source) {
-        if (source == SubscriptionManager.PHONE_NUMBER_SOURCE_UICC) {
-            Phone phone = PhoneFactory.getPhone(getPhoneId(subId));
-            return phone != null ? phone.getLine1Number() : null;
-        }
-        if (source == SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER) {
-            return getSubscriptionProperty(subId, SimInfo.COLUMN_PHONE_NUMBER_SOURCE_CARRIER);
-        }
-        if (source == SubscriptionManager.PHONE_NUMBER_SOURCE_IMS) {
-            return getSubscriptionProperty(subId, SimInfo.COLUMN_PHONE_NUMBER_SOURCE_IMS);
-        }
-        throw new IllegalArgumentException("setPhoneNumber doesn't accept source " + source);
-    }
-
-    /**
-     * Sets the phone number for the given {@code subId}.
-     *
-     * <p>The only accepted {@code source} is {@link
-     * SubscriptionManager#PHONE_NUMBER_SOURCE_CARRIER}.
-     */
-    @Override
-    public void setPhoneNumber(int subId, int source, String number,
-            String callingPackage, String callingFeatureId) {
-        if (source != SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER) {
-            throw new IllegalArgumentException("setPhoneNumber doesn't accept source " + source);
-        }
-        if (!TelephonyPermissions.checkCarrierPrivilegeForSubId(mContext, subId)) {
-            throw new SecurityException("setPhoneNumber for CARRIER needs carrier privilege");
-        }
-        if (number == null) {
-            throw new NullPointerException("invalid number null");
-        }
-
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            setSubscriptionProperty(subId, SimInfo.COLUMN_PHONE_NUMBER_SOURCE_CARRIER, number);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    /**
-     * Set the Usage Setting for this subscription.
-     *
-     * @param usageSetting the cellular usage setting
-     * @param subId the unique SubscriptionInfo index in database
-     * @param callingPackage the package making the IPC
-     * @return the number of records updated
-     *
-     * @throws SecurityException if doesn't have required permission.
-     */
-    @Override
-    public int setUsageSetting(@UsageSetting int usageSetting, int subId, String callingPackage) {
-        try {
-            TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
-                    mContext, subId, callingPackage);
-        } catch (SecurityException e) {
-            enforceCarrierPrivilegeOnInactiveSub(subId, callingPackage,
-                    "Caller requires permission on sub " + subId);
-        }
-
-        if (usageSetting < SubscriptionManager.USAGE_SETTING_DEFAULT
-                || usageSetting > SubscriptionManager.USAGE_SETTING_DATA_CENTRIC) {
-            throw new IllegalArgumentException("setUsageSetting: Invalid usage setting: "
-                    + usageSetting);
-        }
-
-        final long token = Binder.clearCallingIdentity();
-        int ret;
-        try {
-            ret = setSubscriptionProperty(subId, SubscriptionManager.USAGE_SETTING,
-                    String.valueOf(usageSetting));
-
-            // ret is the number of records updated in the DB, which should always be 1.
-            // TODO(b/205027930): move this check prior to the database mutation request
-            if (ret != 1) throw new IllegalArgumentException(
-                    "Invalid SubscriptionId for setUsageSetting");
-        } finally {
-            Binder.restoreCallingIdentity(token);
-            // FIXME(b/205726099) return void
-        }
-        return ret;
-    }
-
-    /**
-     * Querying last used TP - MessageRef for particular subId from SIMInfo table.
-     * @return messageRef
-     */
-    public int getMessageRef(int subId) {
-        try (Cursor cursor = mContext.getContentResolver().query(SubscriptionManager.CONTENT_URI,
-                new String[]{SubscriptionManager.TP_MESSAGE_REF},
-                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=\"" + subId
-                        + "\"", null, null)) {
-            try {
-                if (cursor != null && cursor.moveToFirst()) {
-                    do {
-                        return cursor.getInt(cursor.getColumnIndexOrThrow(
-                                SubscriptionManager.TP_MESSAGE_REF));
-                    } while (cursor.moveToNext());
-                } else {
-                    if (DBG) logd("Valid row not present in db");
-                }
-            } catch (Exception e) {
-                if (DBG) logd("Query failed " + e.getMessage());
-            } finally {
-                if (cursor != null) {
-                    cursor.close();
-                }
-            }
-        }
-        return -1;
-    }
-
-    /**
-     * Update the TP - Message Reference value for every SMS Sent
-     * @param messageRef
-     * @param subId
-     */
-    public void updateMessageRef(int subId, int messageRef) {
-        TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
-                mContext, subId, mContext.getOpPackageName());
-
-        if (mContext == null) {
-            logel("[updateMessageRef] mContext is null");
-            return;
-        }
-        try {
-            if (SubscriptionManager.CONTENT_URI != null) {
-                ContentValues values = new ContentValues(1);
-                values.put(SubscriptionManager.TP_MESSAGE_REF, messageRef);
-                mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, values,
-                        SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=\"" + subId
-                                + "\"", null);
-            } else {
-                if (DBG) logd("TP - Message reference value not updated to DB");
-            }
-        } finally {
-            if (DBG) logd("TP - Message reference updated to DB Successfully :" + messageRef);
-        }
-    }
-
-    /**
-     * Set UserHandle for this subscription
-     *
-     * @param userHandle the userHandle associated with the subscription
-     * Pass {@code null} user handle to clear the association
-     * @param subId the unique SubscriptionInfo index in database
-     * @return the number of records updated.
-     *
-     * @throws SecurityException if doesn't have required permission.
-     * @throws IllegalArgumentException if subId is invalid.
-     */
-    @Override
-    public int setSubscriptionUserHandle(@Nullable UserHandle userHandle, int subId) {
-        enforceManageSubscriptionUserAssociation("setSubscriptionUserHandle");
-
-        if (userHandle == null) {
-            userHandle = UserHandle.of(UserHandle.USER_NULL);
-        }
-
-        long token = Binder.clearCallingIdentity();
-        try {
-            int ret = setSubscriptionProperty(subId, SubscriptionManager.USER_HANDLE,
-                    String.valueOf(userHandle.getIdentifier()));
-            // ret is the number of records updated in the DB
-            if (ret != 0) {
-                notifySubscriptionInfoChanged();
-            } else {
-                throw new IllegalArgumentException("[setSubscriptionUserHandle]: Invalid subId: "
-                        + subId);
-            }
-            return ret;
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    /**
-     * Get UserHandle of this subscription.
-     *
-     * @param subId the unique SubscriptionInfo index in database
-     * @return userHandle associated with this subscription
-     * or {@code null} if subscription is not associated with any user.
-     *
-     * @throws SecurityException if doesn't have required permission.
-     * @throws IllegalArgumentException if subId is invalid.
-     */
-    @Override
-    @Nullable
-    public UserHandle getSubscriptionUserHandle(int subId) {
-        enforceManageSubscriptionUserAssociation("getSubscriptionUserHandle");
-
-        if (!mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_enable_get_subscription_user_handle)) {
-            return null;
-        }
-
-        long token = Binder.clearCallingIdentity();
-        try {
-            String userHandleStr = getSubscriptionProperty(subId, SubscriptionManager.USER_HANDLE);
-            if (userHandleStr == null) {
-                throw new IllegalArgumentException("[getSubscriptionUserHandle]: Invalid subId: "
-                        + subId);
-            }
-            UserHandle userHandle = UserHandle.of(Integer.parseInt(userHandleStr));
-            if (userHandle.getIdentifier() == UserHandle.USER_NULL) {
-                return null;
-            }
-            return userHandle;
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    /**
-     * Check if subscription and user are associated with each other.
-     *
-     * @param subscriptionId the subId of the subscription
-     * @param userHandle user handle of the user
-     * @return {@code true} if subscription is associated with user
-     * {code true} if there are no subscriptions on device
-     * else {@code false} if subscription is not associated with user.
-     *
-     * @throws SecurityException if the caller doesn't have permissions required.
-     * @throws IllegalStateException if subscription service is not available.
-     *
-     */
-    @Override
-    public boolean isSubscriptionAssociatedWithUser(int subscriptionId,
-            @NonNull UserHandle userHandle) {
-        enforceManageSubscriptionUserAssociation("isSubscriptionAssociatedWithUser");
-
-        long token = Binder.clearCallingIdentity();
-        try {
-            // Return true if there are no subscriptions on the device.
-            List<SubscriptionInfo> subInfoList = getAllSubInfoList(
-                    mContext.getOpPackageName(), mContext.getAttributionTag());
-            if (subInfoList == null || subInfoList.isEmpty()) {
-                return true;
-            }
-
-            // Get list of subscriptions associated with this user.
-            List<SubscriptionInfo> associatedSubscriptionsList =
-                    getSubscriptionInfoListAssociatedWithUser(userHandle);
-            if (associatedSubscriptionsList.isEmpty()) {
-                return false;
-            }
-
-            // Return true if required subscription is present in associated subscriptions list.
-            for (SubscriptionInfo subInfo: associatedSubscriptionsList) {
-                if (subInfo.getSubscriptionId() == subscriptionId){
-                    return true;
-                }
-            }
-            return false;
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    /**
-     * Get list of subscriptions associated with user.
-     *
-     * If user handle is associated with some subscriptions, return subscriptionsAssociatedWithUser
-     * else return all the subscriptions which are not associated with any user.
-     *
-     * @param userHandle user handle of the user
-     * @return list of subscriptionInfo associated with the user.
-     *
-     * @throws SecurityException if the caller doesn't have permissions required.
-     * @throws IllegalStateException if subscription service is not available.
-     *
-     */
-    @Override
-    public @NonNull List<SubscriptionInfo> getSubscriptionInfoListAssociatedWithUser(
-            @NonNull UserHandle userHandle) {
-        enforceManageSubscriptionUserAssociation("getActiveSubscriptionInfoListAssociatedWithUser");
-
-        long token = Binder.clearCallingIdentity();
-        try {
-            List<SubscriptionInfo> subInfoList =  getAllSubInfoList(
-                    mContext.getOpPackageName(), mContext.getAttributionTag());
-            if (subInfoList == null || subInfoList.isEmpty()) {
-                return new ArrayList<>();
-            }
-
-            List<SubscriptionInfo> subscriptionsAssociatedWithUser = new ArrayList<>();
-            List<SubscriptionInfo> subscriptionsWithNoAssociation = new ArrayList<>();
-            for (SubscriptionInfo subInfo : subInfoList) {
-                int subId = subInfo.getSubscriptionId();
-                UserHandle subIdUserHandle = getSubscriptionUserHandle(subId);
-                if (userHandle.equals(subIdUserHandle)) {
-                    // Store subscriptions whose user handle matches with required user handle.
-                    subscriptionsAssociatedWithUser.add(subInfo);
-                } else if (subIdUserHandle == null) {
-                    // Store subscriptions whose user handle is set to null.
-                    subscriptionsWithNoAssociation.add(subInfo);
-                }
-            }
-
-            return subscriptionsAssociatedWithUser.isEmpty() ?
-                    subscriptionsWithNoAssociation : subscriptionsAssociatedWithUser;
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    /**
-     * @return {@code true} if using {@link SubscriptionManagerService} instead of
-     * {@link SubscriptionController}.
-     */
-    //TODO: Removed before U AOSP public release.
-    @Override
-    public boolean isSubscriptionManagerServiceEnabled() {
-        return false;
-    }
-
-    /**
-     * Called during setup wizard restore flow to attempt to restore the backed up sim-specific
-     * configs to device for all existing SIMs in the subscription database {@link SimInfo}.
-     * Internally, it will store the backup data in an internal file. This file will persist on
-     * device for device's lifetime and will be used later on when a SIM is inserted to restore that
-     * specific SIM's settings. End result is subscription database is modified to match any backed
-     * up configs for the appropriate inserted SIMs.
-     *
-     * <p>
-     * The {@link Uri} {@link SubscriptionManager#SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI} is
-     * notified if any {@link SimInfo} entry is updated as the result of this method call.
-     *
-     * @param data with the sim specific configs to be backed up.
-     */
-    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
-    @Override
-    public void restoreAllSimSpecificSettingsFromBackup(@NonNull byte[] data) {
-        enforceModifyPhoneState("restoreAllSimSpecificSettingsFromBackup");
-
-        long token = Binder.clearCallingIdentity();
-        try {
-            Bundle bundle = new Bundle();
-            bundle.putByteArray(SubscriptionManager.KEY_SIM_SPECIFIC_SETTINGS_DATA, data);
-            mContext.getContentResolver().call(
-                    SubscriptionManager.SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI,
-                    SubscriptionManager.RESTORE_SIM_SPECIFIC_SETTINGS_METHOD_NAME,
-                    null, bundle);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
-    /**
-     * @hide
-     */
-    private void setGlobalSetting(String name, int value) {
-        Settings.Global.putInt(mContext.getContentResolver(), name, value);
-        if (TextUtils.equals(name, Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION)) {
-            invalidateDefaultDataSubIdCaches();
-            invalidateActiveDataSubIdCaches();
-            invalidateDefaultSubIdCaches();
-            invalidateSlotIndexCaches();
-        } else if (TextUtils.equals(name, Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION)) {
-            invalidateDefaultSubIdCaches();
-            invalidateSlotIndexCaches();
-        } else if (TextUtils.equals(name, Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION)) {
-            invalidateDefaultSmsSubIdCaches();
-        }
-    }
-
-    private static void invalidateDefaultSubIdCaches() {
-        SubscriptionManager.invalidateDefaultSubIdCaches();
-    }
-
-    private static void invalidateDefaultDataSubIdCaches() {
-        SubscriptionManager.invalidateDefaultDataSubIdCaches();
-    }
-
-    private static void invalidateDefaultSmsSubIdCaches() {
-        SubscriptionManager.invalidateDefaultSmsSubIdCaches();
-    }
-
-    private static void invalidateActiveDataSubIdCaches() {
-        SubscriptionManager.invalidateActiveDataSubIdCaches();
-    }
-
-    private static void invalidateSlotIndexCaches() {
-        SubscriptionManager.invalidateSlotIndexCaches();
-    }
-}
diff --git a/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java b/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
deleted file mode 100644
index ea49d73..0000000
--- a/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
+++ /dev/null
@@ -1,1358 +0,0 @@
-/*
-* Copyright (C) 2014 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.Manifest;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.compat.annotation.UnsupportedAppUsage;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.content.res.Resources;
-import android.os.AsyncResult;
-import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.ParcelUuid;
-import android.os.PersistableBundle;
-import android.os.UserHandle;
-import android.preference.PreferenceManager;
-import android.service.carrier.CarrierIdentifier;
-import android.service.euicc.EuiccProfileInfo;
-import android.service.euicc.EuiccService;
-import android.service.euicc.GetEuiccProfileInfoListResult;
-import android.telephony.CarrierConfigManager;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.telephony.SubscriptionManager.UsageSetting;
-import android.telephony.TelephonyManager;
-import android.telephony.UiccAccessRule;
-import android.telephony.euicc.EuiccManager;
-import android.text.TextUtils;
-import android.util.Pair;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.euicc.EuiccController;
-import com.android.internal.telephony.metrics.TelephonyMetrics;
-import com.android.internal.telephony.uicc.IccRecords;
-import com.android.internal.telephony.uicc.IccUtils;
-import com.android.internal.telephony.uicc.UiccCard;
-import com.android.internal.telephony.uicc.UiccController;
-import com.android.internal.telephony.uicc.UiccPort;
-import com.android.internal.telephony.uicc.UiccSlot;
-import com.android.telephony.Rlog;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArraySet;
-
-/**
- *@hide
- */
-public class SubscriptionInfoUpdater extends Handler {
-    private static final String LOG_TAG = "SubscriptionInfoUpdater";
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private static final int SUPPORTED_MODEM_COUNT = TelephonyManager.getDefault()
-            .getSupportedModemCount();
-
-    private static final boolean DBG = true;
-
-    private static final int EVENT_INVALID = -1;
-    private static final int EVENT_GET_NETWORK_SELECTION_MODE_DONE = 2;
-    private static final int EVENT_SIM_LOADED = 3;
-    private static final int EVENT_SIM_ABSENT = 4;
-    private static final int EVENT_SIM_LOCKED = 5;
-    private static final int EVENT_SIM_IO_ERROR = 6;
-    private static final int EVENT_SIM_UNKNOWN = 7;
-    private static final int EVENT_SIM_RESTRICTED = 8;
-    private static final int EVENT_SIM_NOT_READY = 9;
-    private static final int EVENT_SIM_READY = 10;
-    private static final int EVENT_SIM_IMSI = 11;
-    private static final int EVENT_REFRESH_EMBEDDED_SUBSCRIPTIONS = 12;
-    private static final int EVENT_MULTI_SIM_CONFIG_CHANGED = 13;
-    private static final int EVENT_INACTIVE_SLOT_ICC_STATE_CHANGED = 14;
-
-    private static final String ICCID_STRING_FOR_NO_SIM = "";
-
-    private static final ParcelUuid REMOVE_GROUP_UUID =
-            ParcelUuid.fromString(CarrierConfigManager.REMOVE_GROUP_UUID_STRING);
-
-    // Key used to read/write the current IMSI. Updated on SIM_STATE_CHANGED - LOADED.
-    public static final String CURR_SUBID = "curr_subid";
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private static Context sContext = null;
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-
-    protected static String[] sIccId = new String[SUPPORTED_MODEM_COUNT];
-    protected SubscriptionController mSubscriptionController = null;
-    private static String[] sInactiveIccIds = new String[SUPPORTED_MODEM_COUNT];
-    private static int[] sSimCardState = new int[SUPPORTED_MODEM_COUNT];
-    private static int[] sSimApplicationState = new int[SUPPORTED_MODEM_COUNT];
-    private static boolean sIsSubInfoInitialized = false;
-    private SubscriptionManager mSubscriptionManager = null;
-    private EuiccManager mEuiccManager;
-    private Handler mBackgroundHandler;
-
-    // The current foreground user ID.
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private int mCurrentlyActiveUserId;
-    private CarrierServiceBindHelper mCarrierServiceBindHelper;
-
-    private volatile boolean shouldRetryUpdateEmbeddedSubscriptions = false;
-    private final CopyOnWriteArraySet<Integer> retryUpdateEmbeddedSubscriptionCards =
-        new CopyOnWriteArraySet<>();
-    private final BroadcastReceiver mUserUnlockedReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
-                // The LPA may not have been ready before user unlock, and so previous attempts
-                // to refresh the list of embedded subscriptions may have failed. This retries
-                // the refresh operation after user unlock.
-                if (shouldRetryUpdateEmbeddedSubscriptions) {
-                    logd("Retrying refresh embedded subscriptions after user unlock.");
-                    for (int cardId : retryUpdateEmbeddedSubscriptionCards){
-                        requestEmbeddedSubscriptionInfoListRefresh(cardId, null);
-                    }
-                    retryUpdateEmbeddedSubscriptionCards.clear();
-                    sContext.unregisterReceiver(mUserUnlockedReceiver);
-                }
-            }
-        }
-    };
-
-    /**
-     * Runnable with a boolean parameter. This is used in
-     * updateEmbeddedSubscriptions(List<Integer> cardIds, @Nullable UpdateEmbeddedSubsCallback).
-     */
-    protected interface UpdateEmbeddedSubsCallback {
-        /**
-         * Callback of the Runnable.
-         * @param hasChanges Whether there is any subscription info change. If yes, we need to
-         * notify the listeners.
-         */
-        void run(boolean hasChanges);
-    }
-
-    @VisibleForTesting
-    public SubscriptionInfoUpdater(Looper looper, Context context, SubscriptionController sc) {
-        logd("Constructor invoked");
-        mBackgroundHandler = new Handler(looper);
-
-        sContext = context;
-        mSubscriptionController = sc;
-        mSubscriptionManager = SubscriptionManager.from(sContext);
-        mEuiccManager = (EuiccManager) sContext.getSystemService(Context.EUICC_SERVICE);
-
-        mCarrierServiceBindHelper = new CarrierServiceBindHelper(sContext);
-
-        sContext.registerReceiver(
-                mUserUnlockedReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
-
-        initializeCarrierApps();
-
-        PhoneConfigurationManager.registerForMultiSimConfigChange(
-                this, EVENT_MULTI_SIM_CONFIG_CHANGED, null);
-    }
-
-    private void initializeCarrierApps() {
-        // Initialize carrier apps:
-        // -Now (on system startup)
-        // -Whenever new carrier privilege rules might change (new SIM is loaded)
-        // -Whenever we switch to a new user
-        mCurrentlyActiveUserId = 0;
-        sContext.registerReceiverForAllUsers(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                // Remove this line after testing
-                if (Intent.ACTION_USER_FOREGROUND.equals(intent.getAction())) {
-                    UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER);
-                    // If couldn't get current user ID, guess it's 0.
-                    mCurrentlyActiveUserId = userHandle != null ? userHandle.getIdentifier() : 0;
-                    CarrierAppUtils.disableCarrierAppsUntilPrivileged(sContext.getOpPackageName(),
-                            TelephonyManager.getDefault(), mCurrentlyActiveUserId, sContext);
-                }
-            }
-        }, new IntentFilter(Intent.ACTION_USER_FOREGROUND), null, null);
-        ActivityManager am = (ActivityManager) sContext.getSystemService(Context.ACTIVITY_SERVICE);
-        mCurrentlyActiveUserId = am.getCurrentUser();
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(sContext.getOpPackageName(),
-                TelephonyManager.getDefault(), mCurrentlyActiveUserId, sContext);
-    }
-
-    /**
-     * Update subscriptions when given a new ICC state.
-     */
-    public void updateInternalIccState(String simStatus, String reason, int phoneId) {
-        logd("updateInternalIccState to simStatus " + simStatus + " reason " + reason
-                + " phoneId " + phoneId);
-        int message = internalIccStateToMessage(simStatus);
-        if (message != EVENT_INVALID) {
-            sendMessage(obtainMessage(message, phoneId, 0, reason));
-        }
-    }
-
-    /**
-     * Update subscriptions if needed when there's a change in inactive port.
-     * @param prevActivePhoneId is the corresponding phoneId of the port if port was previously
-     *                          active. It could be INVALID if it was already inactive.
-     * @param iccId iccId in that port, if any.
-     */
-    public void updateInternalIccStateForInactivePort(int prevActivePhoneId, String iccId) {
-        sendMessage(obtainMessage(EVENT_INACTIVE_SLOT_ICC_STATE_CHANGED, prevActivePhoneId,
-                0, iccId));
-    }
-
-    private int internalIccStateToMessage(String simStatus) {
-        switch(simStatus) {
-            case IccCardConstants.INTENT_VALUE_ICC_ABSENT: return EVENT_SIM_ABSENT;
-            case IccCardConstants.INTENT_VALUE_ICC_UNKNOWN: return EVENT_SIM_UNKNOWN;
-            case IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR: return EVENT_SIM_IO_ERROR;
-            case IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED: return EVENT_SIM_RESTRICTED;
-            case IccCardConstants.INTENT_VALUE_ICC_NOT_READY: return EVENT_SIM_NOT_READY;
-            case IccCardConstants.INTENT_VALUE_ICC_LOCKED: return EVENT_SIM_LOCKED;
-            case IccCardConstants.INTENT_VALUE_ICC_LOADED: return EVENT_SIM_LOADED;
-            case IccCardConstants.INTENT_VALUE_ICC_READY: return EVENT_SIM_READY;
-            case IccCardConstants.INTENT_VALUE_ICC_IMSI: return EVENT_SIM_IMSI;
-            default:
-                logd("Ignoring simStatus: " + simStatus);
-                return EVENT_INVALID;
-        }
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    protected boolean isAllIccIdQueryDone() {
-        for (int i = 0; i < TelephonyManager.getDefault().getActiveModemCount(); i++) {
-            UiccSlot slot = UiccController.getInstance().getUiccSlotForPhone(i);
-            int slotId = UiccController.getInstance().getSlotIdFromPhoneId(i);
-            // When psim card is absent there is no port object even the port state is active.
-            // We should check the slot state for psim and port state for esim(MEP eUICC).
-            if  (sIccId[i] == null || slot == null || !slot.isActive()
-                    || (slot.isEuicc() && UiccController.getInstance().getUiccPort(i) == null)) {
-                if (sIccId[i] == null) {
-                    logd("Wait for SIM " + i + " Iccid");
-                } else {
-                    logd(String.format("Wait for port corresponding to phone %d to be active, "
-                        + "slotId is %d" + " , portIndex is %d", i, slotId,
-                            slot.getPortIndexFromPhoneId(i)));
-                }
-                return false;
-            }
-        }
-        logd("All IccIds query complete");
-
-        return true;
-    }
-
-    @Override
-    public void handleMessage(Message msg) {
-        List<Integer> cardIds = new ArrayList<>();
-        switch (msg.what) {
-            case EVENT_GET_NETWORK_SELECTION_MODE_DONE: {
-                AsyncResult ar = (AsyncResult)msg.obj;
-                Integer slotId = (Integer)ar.userObj;
-                if (ar.exception == null && ar.result != null) {
-                    int[] modes = (int[])ar.result;
-                    if (modes[0] == 1) {  // Manual mode.
-                        PhoneFactory.getPhone(slotId).setNetworkSelectionModeAutomatic(null);
-                    }
-                } else {
-                    logd("EVENT_GET_NETWORK_SELECTION_MODE_DONE: error getting network mode.");
-                }
-                break;
-            }
-
-            case EVENT_SIM_LOADED:
-                handleSimLoaded(msg.arg1);
-                break;
-
-            case EVENT_SIM_ABSENT:
-                handleSimAbsent(msg.arg1);
-                break;
-
-            case EVENT_INACTIVE_SLOT_ICC_STATE_CHANGED:
-                handleInactivePortIccStateChange(msg.arg1, (String) msg.obj);
-                break;
-
-            case EVENT_SIM_LOCKED:
-                handleSimLocked(msg.arg1, (String) msg.obj);
-                break;
-
-            case EVENT_SIM_UNKNOWN:
-                broadcastSimStateChanged(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_UNKNOWN, null);
-                broadcastSimCardStateChanged(msg.arg1, TelephonyManager.SIM_STATE_UNKNOWN);
-                broadcastSimApplicationStateChanged(msg.arg1, TelephonyManager.SIM_STATE_UNKNOWN);
-                updateSubscriptionCarrierId(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_UNKNOWN);
-                updateCarrierServices(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_UNKNOWN);
-                break;
-
-            case EVENT_SIM_IO_ERROR:
-                handleSimError(msg.arg1);
-                break;
-
-            case EVENT_SIM_RESTRICTED:
-                broadcastSimStateChanged(msg.arg1,
-                        IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED,
-                        IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED);
-                broadcastSimCardStateChanged(msg.arg1, TelephonyManager.SIM_STATE_CARD_RESTRICTED);
-                broadcastSimApplicationStateChanged(msg.arg1, TelephonyManager.SIM_STATE_NOT_READY);
-                updateSubscriptionCarrierId(msg.arg1,
-                        IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED);
-                updateCarrierServices(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED);
-                break;
-
-            case EVENT_SIM_READY:
-                handleSimReady(msg.arg1);
-                break;
-
-            case EVENT_SIM_IMSI:
-                broadcastSimStateChanged(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_IMSI, null);
-                break;
-
-            case EVENT_SIM_NOT_READY:
-                // an eUICC with no active subscriptions never becomes ready, so we need to trigger
-                // the embedded subscriptions update here
-                cardIds.add(getCardIdFromPhoneId(msg.arg1));
-                updateEmbeddedSubscriptions(cardIds, (hasChanges) -> {
-                    if (hasChanges) {
-                        mSubscriptionController.notifySubscriptionInfoChanged();
-                    }
-                });
-                handleSimNotReady(msg.arg1);
-                break;
-
-            case EVENT_REFRESH_EMBEDDED_SUBSCRIPTIONS:
-                cardIds.add(msg.arg1);
-                Runnable r = (Runnable) msg.obj;
-                updateEmbeddedSubscriptions(cardIds, (hasChanges) -> {
-                    if (hasChanges) {
-                        mSubscriptionController.notifySubscriptionInfoChanged();
-                    }
-                    if (r != null) {
-                        r.run();
-                    }
-                });
-                break;
-
-            case EVENT_MULTI_SIM_CONFIG_CHANGED:
-                onMultiSimConfigChanged();
-                break;
-
-            default:
-                logd("Unknown msg:" + msg.what);
-        }
-    }
-
-    private void onMultiSimConfigChanged() {
-        int activeModemCount = ((TelephonyManager) sContext.getSystemService(
-                Context.TELEPHONY_SERVICE)).getActiveModemCount();
-        // For inactive modems, reset its states.
-        for (int phoneId = activeModemCount; phoneId < SUPPORTED_MODEM_COUNT; phoneId++) {
-            sIccId[phoneId] = null;
-            sSimCardState[phoneId] = TelephonyManager.SIM_STATE_UNKNOWN;
-            sSimApplicationState[phoneId] = TelephonyManager.SIM_STATE_UNKNOWN;
-        }
-    }
-
-    protected int getCardIdFromPhoneId(int phoneId) {
-        UiccController uiccController = UiccController.getInstance();
-        UiccCard card = uiccController.getUiccCardForPhone(phoneId);
-        if (card != null) {
-            return uiccController.convertToPublicCardId(card.getCardId());
-        }
-        return TelephonyManager.UNINITIALIZED_CARD_ID;
-    }
-
-    void requestEmbeddedSubscriptionInfoListRefresh(int cardId, @Nullable Runnable callback) {
-        sendMessage(obtainMessage(
-                EVENT_REFRESH_EMBEDDED_SUBSCRIPTIONS, cardId, 0 /* arg2 */, callback));
-    }
-
-    protected void handleSimLocked(int phoneId, String reason) {
-        if (sIccId[phoneId] != null && sIccId[phoneId].equals(ICCID_STRING_FOR_NO_SIM)) {
-            logd("SIM" + (phoneId + 1) + " hot plug in");
-            sIccId[phoneId] = null;
-        }
-
-        IccCard iccCard = PhoneFactory.getPhone(phoneId).getIccCard();
-        if (iccCard == null) {
-            logd("handleSimLocked: IccCard null");
-            return;
-        }
-        IccRecords records = iccCard.getIccRecords();
-        if (records == null) {
-            logd("handleSimLocked: IccRecords null");
-            return;
-        }
-        if (IccUtils.stripTrailingFs(records.getFullIccId()) == null) {
-            logd("handleSimLocked: IccID null");
-            return;
-        }
-        sIccId[phoneId] = IccUtils.stripTrailingFs(records.getFullIccId());
-
-        updateSubscriptionInfoByIccId(phoneId, true /* updateEmbeddedSubs */);
-
-        broadcastSimStateChanged(phoneId, IccCardConstants.INTENT_VALUE_ICC_LOCKED, reason);
-        broadcastSimCardStateChanged(phoneId, TelephonyManager.SIM_STATE_PRESENT);
-        broadcastSimApplicationStateChanged(phoneId, getSimStateFromLockedReason(reason));
-        updateSubscriptionCarrierId(phoneId, IccCardConstants.INTENT_VALUE_ICC_LOCKED);
-        updateCarrierServices(phoneId, IccCardConstants.INTENT_VALUE_ICC_LOCKED);
-    }
-
-    private static int getSimStateFromLockedReason(String lockedReason) {
-        switch (lockedReason) {
-            case IccCardConstants.INTENT_VALUE_LOCKED_ON_PIN:
-                return TelephonyManager.SIM_STATE_PIN_REQUIRED;
-            case IccCardConstants.INTENT_VALUE_LOCKED_ON_PUK:
-                return TelephonyManager.SIM_STATE_PUK_REQUIRED;
-            case IccCardConstants.INTENT_VALUE_LOCKED_NETWORK:
-                return TelephonyManager.SIM_STATE_NETWORK_LOCKED;
-            case IccCardConstants.INTENT_VALUE_ABSENT_ON_PERM_DISABLED:
-                return TelephonyManager.SIM_STATE_PERM_DISABLED;
-            default:
-                Rlog.e(LOG_TAG, "Unexpected SIM locked reason " + lockedReason);
-                return TelephonyManager.SIM_STATE_UNKNOWN;
-        }
-    }
-
-    protected void handleSimReady(int phoneId) {
-        List<Integer> cardIds = new ArrayList<>();
-        logd("handleSimReady: phoneId: " + phoneId);
-
-        if (sIccId[phoneId] != null && sIccId[phoneId].equals(ICCID_STRING_FOR_NO_SIM)) {
-            logd(" SIM" + (phoneId + 1) + " hot plug in");
-            sIccId[phoneId] = null;
-        }
-
-        // ICCID is not available in IccRecords by the time SIM Ready event received
-        // hence get ICCID from UiccPort.
-        UiccPort port = UiccController.getInstance().getUiccPort(phoneId);
-        String iccId = (port == null) ? null : IccUtils.stripTrailingFs(port.getIccId());
-
-        if (!TextUtils.isEmpty(iccId)) {
-            sIccId[phoneId] = iccId;
-            updateSubscriptionInfoByIccId(phoneId, true /* updateEmbeddedSubs */);
-        }
-
-        cardIds.add(getCardIdFromPhoneId(phoneId));
-        updateEmbeddedSubscriptions(cardIds, (hasChanges) -> {
-            if (hasChanges) {
-                mSubscriptionController.notifySubscriptionInfoChanged();
-            }
-        });
-        broadcastSimStateChanged(phoneId, IccCardConstants.INTENT_VALUE_ICC_READY, null);
-        broadcastSimCardStateChanged(phoneId, TelephonyManager.SIM_STATE_PRESENT);
-        broadcastSimApplicationStateChanged(phoneId, TelephonyManager.SIM_STATE_NOT_READY);
-    }
-
-    protected void handleSimNotReady(int phoneId) {
-        logd("handleSimNotReady: phoneId: " + phoneId);
-        boolean isFinalState = false;
-
-        IccCard iccCard = PhoneFactory.getPhone(phoneId).getIccCard();
-        boolean uiccAppsDisabled = areUiccAppsDisabledOnCard(phoneId);
-        if (iccCard.isEmptyProfile() || uiccAppsDisabled) {
-            if (uiccAppsDisabled) {
-                UiccPort port = UiccController.getInstance().getUiccPort(phoneId);
-                String iccId = (port == null) ? null : port.getIccId();
-                sInactiveIccIds[phoneId] = IccUtils.stripTrailingFs(iccId);
-            }
-            isFinalState = true;
-            // ICC_NOT_READY is a terminal state for
-            // 1) It's an empty profile as there's no uicc applications. Or
-            // 2) Its uicc applications are set to be disabled.
-            // At this phase, the subscription list is accessible. Treating NOT_READY
-            // as equivalent to ABSENT, once the rest of the system can handle it.
-            sIccId[phoneId] = ICCID_STRING_FOR_NO_SIM;
-            updateSubscriptionInfoByIccId(phoneId, false /* updateEmbeddedSubs */);
-        } else {
-            sIccId[phoneId] = null;
-        }
-
-        broadcastSimStateChanged(phoneId, IccCardConstants.INTENT_VALUE_ICC_NOT_READY,
-                null);
-        broadcastSimCardStateChanged(phoneId, TelephonyManager.SIM_STATE_PRESENT);
-        broadcastSimApplicationStateChanged(phoneId, TelephonyManager.SIM_STATE_NOT_READY);
-        if (isFinalState) {
-            updateCarrierServices(phoneId, IccCardConstants.INTENT_VALUE_ICC_NOT_READY);
-        }
-    }
-
-    private boolean areUiccAppsDisabledOnCard(int phoneId) {
-        // When uicc apps are disabled(supported in IRadio 1.5), we will still get IccId from
-        // cardStatus (since IRadio 1.2). Amd upon cardStatus change we'll receive another
-        // handleSimNotReady so this will be evaluated again.
-        UiccSlot slot = UiccController.getInstance().getUiccSlotForPhone(phoneId);
-        if (slot == null) return false;
-        UiccPort port = UiccController.getInstance().getUiccPort(phoneId);
-        String iccId = (port == null) ? null : port.getIccId();
-        if (iccId == null) {
-            return false;
-        }
-        SubscriptionInfo info =
-                mSubscriptionController.getSubInfoForIccId(
-                        IccUtils.stripTrailingFs(iccId));
-        return info != null && !info.areUiccApplicationsEnabled();
-    }
-
-    protected void handleSimLoaded(int phoneId) {
-        logd("handleSimLoaded: phoneId: " + phoneId);
-
-        // The SIM should be loaded at this state, but it is possible in cases such as SIM being
-        // removed or a refresh RESET that the IccRecords could be null. The right behavior is to
-        // not broadcast the SIM loaded.
-        IccCard iccCard = PhoneFactory.getPhone(phoneId).getIccCard();
-        if (iccCard == null) {  // Possibly a race condition.
-            logd("handleSimLoaded: IccCard null");
-            return;
-        }
-        IccRecords records = iccCard.getIccRecords();
-        if (records == null) {  // Possibly a race condition.
-            logd("handleSimLoaded: IccRecords null");
-            return;
-        }
-        if (IccUtils.stripTrailingFs(records.getFullIccId()) == null) {
-            logd("handleSimLoaded: IccID null");
-            return;
-        }
-
-        // Call updateSubscriptionInfoByIccId() only if was not done earlier from SIM READY event
-        if (sIccId[phoneId] == null) {
-            sIccId[phoneId] = IccUtils.stripTrailingFs(records.getFullIccId());
-
-            updateSubscriptionInfoByIccId(phoneId, true /* updateEmbeddedSubs */);
-        }
-
-        List<SubscriptionInfo> subscriptionInfos =
-                mSubscriptionController.getSubInfoUsingSlotIndexPrivileged(phoneId);
-        if (subscriptionInfos == null || subscriptionInfos.isEmpty()) {
-            loge("empty subinfo for phoneId: " + phoneId + "could not update ContentResolver");
-        } else {
-            for (SubscriptionInfo sub : subscriptionInfos) {
-                int subId = sub.getSubscriptionId();
-                TelephonyManager tm = (TelephonyManager)
-                        sContext.getSystemService(Context.TELEPHONY_SERVICE);
-                String operator = tm.getSimOperatorNumeric(subId);
-
-                if (!TextUtils.isEmpty(operator)) {
-                    if (subId == mSubscriptionController.getDefaultSubId()) {
-                        MccTable.updateMccMncConfiguration(sContext, operator);
-                    }
-                    mSubscriptionController.setMccMnc(operator, subId);
-                } else {
-                    logd("EVENT_RECORDS_LOADED Operator name is null");
-                }
-
-                String iso = tm.getSimCountryIsoForPhone(phoneId);
-
-                if (!TextUtils.isEmpty(iso)) {
-                    mSubscriptionController.setCountryIso(iso, subId);
-                } else {
-                    logd("EVENT_RECORDS_LOADED sim country iso is null");
-                }
-
-                String msisdn = tm.getLine1Number(subId);
-                if (msisdn != null) {
-                    mSubscriptionController.setDisplayNumber(msisdn, subId);
-                }
-
-                String imsi = tm.createForSubscriptionId(subId).getSubscriberId();
-                if (imsi != null) {
-                    mSubscriptionController.setImsi(imsi, subId);
-                }
-
-                String[] ehplmns = records.getEhplmns();
-                String[] hplmns = records.getPlmnsFromHplmnActRecord();
-                if (ehplmns != null || hplmns != null) {
-                    mSubscriptionController.setAssociatedPlmns(ehplmns, hplmns, subId);
-                }
-
-                /* Update preferred network type and network selection mode on SIM change.
-                 * Storing last subId in SharedPreference for now to detect SIM change.
-                 */
-                SharedPreferences sp =
-                        PreferenceManager.getDefaultSharedPreferences(sContext);
-                int storedSubId = sp.getInt(CURR_SUBID + phoneId, -1);
-
-                if (storedSubId != subId) {
-                    // Only support automatic selection mode on SIM change.
-                    PhoneFactory.getPhone(phoneId).getNetworkSelectionMode(
-                            obtainMessage(EVENT_GET_NETWORK_SELECTION_MODE_DONE,
-                                    new Integer(phoneId)));
-                    // Update stored subId
-                    SharedPreferences.Editor editor = sp.edit();
-                    editor.putInt(CURR_SUBID + phoneId, subId);
-                    editor.apply();
-                }
-            }
-        }
-
-        /**
-         * The sim loading sequence will be
-         *  1. OnSubscriptionsChangedListener is called through updateSubscriptionInfoByIccId()
-         *  above.
-         *  2. ACTION_SIM_STATE_CHANGED/ACTION_SIM_CARD_STATE_CHANGED
-         *  /ACTION_SIM_APPLICATION_STATE_CHANGED
-         *  3. ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED
-         *  4. restore sim-specific settings
-         *  5. ACTION_CARRIER_CONFIG_CHANGED
-         */
-        broadcastSimStateChanged(phoneId, IccCardConstants.INTENT_VALUE_ICC_LOADED, null);
-        broadcastSimCardStateChanged(phoneId, TelephonyManager.SIM_STATE_PRESENT);
-        broadcastSimApplicationStateChanged(phoneId, TelephonyManager.SIM_STATE_LOADED);
-        updateSubscriptionCarrierId(phoneId, IccCardConstants.INTENT_VALUE_ICC_LOADED);
-        /* Sim-specific settings restore depends on knowing both the mccmnc and the carrierId of the
-        sim which is why it must be done after #updateSubscriptionCarrierId(). It is done before
-        carrier config update to avoid any race conditions with user settings that depend on
-        carrier config*/
-        restoreSimSpecificSettingsForPhone(phoneId);
-        updateCarrierServices(phoneId, IccCardConstants.INTENT_VALUE_ICC_LOADED);
-    }
-
-    /**
-     * Calculate the usage setting based on the carrier request.
-     *
-     * @param currentUsageSetting the current setting in the subscription DB
-     * @param preferredUsageSetting provided by the carrier config
-     * @return the calculated usage setting.
-     */
-    @VisibleForTesting
-    @UsageSetting public int calculateUsageSetting(
-            @UsageSetting int currentUsageSetting, @UsageSetting int preferredUsageSetting) {
-        int defaultUsageSetting;
-        int[] supportedUsageSettings;
-
-        //  Load the resources to provide the device capability
-        try {
-            defaultUsageSetting = sContext.getResources().getInteger(
-                com.android.internal.R.integer.config_default_cellular_usage_setting);
-            supportedUsageSettings = sContext.getResources().getIntArray(
-                com.android.internal.R.array.config_supported_cellular_usage_settings);
-            // If usage settings are not supported, return the default setting, which is UNKNOWN.
-            if (supportedUsageSettings == null
-                    || supportedUsageSettings.length < 1) return currentUsageSetting;
-        } catch (Resources.NotFoundException nfe) {
-            loge("Failed to load usage setting resources!");
-            return currentUsageSetting;
-        }
-
-        // If the current setting is invalid, including the first time the value is set,
-        // update it to default (this will trigger a change in the DB).
-        if (currentUsageSetting < SubscriptionManager.USAGE_SETTING_DEFAULT
-                || currentUsageSetting > SubscriptionManager.USAGE_SETTING_DATA_CENTRIC) {
-            logd("Updating usage setting for current subscription");
-            currentUsageSetting = SubscriptionManager.USAGE_SETTING_DEFAULT;
-        }
-
-        // Range check the inputs, and on failure, make no changes
-        if (preferredUsageSetting < SubscriptionManager.USAGE_SETTING_DEFAULT
-                || preferredUsageSetting > SubscriptionManager.USAGE_SETTING_DATA_CENTRIC) {
-            loge("Invalid usage setting!" + preferredUsageSetting);
-            return currentUsageSetting;
-        }
-
-        // Default is always allowed
-        if (preferredUsageSetting == SubscriptionManager.USAGE_SETTING_DEFAULT) {
-            return preferredUsageSetting;
-        }
-
-        // Forced setting must be explicitly supported
-        for (int i = 0; i < supportedUsageSettings.length; i++) {
-            if (preferredUsageSetting == supportedUsageSettings[i]) return preferredUsageSetting;
-        }
-
-        // If the preferred setting is not possible, just keep the current setting.
-        return currentUsageSetting;
-    }
-
-    private void restoreSimSpecificSettingsForPhone(int phoneId) {
-        sContext.getContentResolver().call(
-                SubscriptionManager.SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI,
-                SubscriptionManager.RESTORE_SIM_SPECIFIC_SETTINGS_METHOD_NAME,
-                sIccId[phoneId], null);
-    }
-
-    private void updateCarrierServices(int phoneId, String simState) {
-        if (!SubscriptionManager.isValidPhoneId(phoneId)) {
-            logd("Ignore updateCarrierServices request with invalid phoneId " + phoneId);
-            return;
-        }
-        CarrierConfigManager configManager =
-                (CarrierConfigManager) sContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        configManager.updateConfigForPhoneId(phoneId, simState);
-        mCarrierServiceBindHelper.updateForPhoneId(phoneId, simState);
-    }
-
-    private void updateSubscriptionCarrierId(int phoneId, String simState) {
-        if (PhoneFactory.getPhone(phoneId) != null) {
-            PhoneFactory.getPhone(phoneId).resolveSubscriptionCarrierId(simState);
-        }
-    }
-
-    /**
-     * PhoneId is the corresponding phoneId of the port if port was previously active.
-     * It could be INVALID if it was already inactive.
-     */
-    private void handleInactivePortIccStateChange(int phoneId, String iccId) {
-        if (SubscriptionManager.isValidPhoneId(phoneId)) {
-            // If phoneId is valid, it means the physical slot was previously active in that
-            // phoneId. In this case, found the subId and set its phoneId to invalid.
-            if (sIccId[phoneId] != null && !sIccId[phoneId].equals(ICCID_STRING_FOR_NO_SIM)) {
-                logd("Slot of SIM" + (phoneId + 1) + " becomes inactive");
-            }
-            cleanSubscriptionInPhone(phoneId, false);
-        }
-        if (!TextUtils.isEmpty(iccId)) {
-            // If iccId is new, add a subscription record in the db.
-            String strippedIccId = IccUtils.stripTrailingFs(iccId);
-            if (mSubscriptionController.getSubInfoForIccId(strippedIccId) == null) {
-                mSubscriptionController.insertEmptySubInfoRecord(
-                        strippedIccId, "CARD", SubscriptionManager.INVALID_PHONE_INDEX,
-                        SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
-            }
-        }
-    }
-
-    /**
-     * Clean subscription info when sim state becomes ABSENT. There are 2 scenarios for this:
-     * 1. SIM is actually removed
-     * 2. Slot becomes inactive, which results in SIM being treated as ABSENT, but SIM may not
-     * have been removed.
-     * @param phoneId phoneId for which the cleanup needs to be done
-     * @param isSimAbsent boolean to indicate if the SIM is actually ABSENT (case 1 above)
-     */
-    private void cleanSubscriptionInPhone(int phoneId, boolean isSimAbsent) {
-        if (sInactiveIccIds[phoneId] != null || (isSimAbsent && sIccId[phoneId] != null
-                && !sIccId[phoneId].equals(ICCID_STRING_FOR_NO_SIM))) {
-            // When a SIM is unplugged, mark uicc applications enabled. This is to make sure when
-            // user unplugs and re-inserts the SIM card, we re-enable it.
-            // In certain cases this can happen before sInactiveIccIds is updated, which is why we
-            // check for sIccId as well (in case of isSimAbsent). The scenario is: after SIM
-            // deactivate request is sent to RIL, SIM is removed before SIM state is updated to
-            // NOT_READY. We do not need to check if this exact scenario is hit, because marking
-            // uicc applications enabled when SIM is removed should be okay to do regardless.
-            logd("cleanSubscriptionInPhone: " + phoneId + ", inactive iccid "
-                    + sInactiveIccIds[phoneId]);
-            if (sInactiveIccIds[phoneId] == null) {
-                logd("cleanSubscriptionInPhone: " + phoneId + ", isSimAbsent=" + isSimAbsent
-                        + ", iccid=" + sIccId[phoneId]);
-            }
-            String iccId = sInactiveIccIds[phoneId] != null
-                    ? sInactiveIccIds[phoneId] : sIccId[phoneId];
-            ContentValues value = new ContentValues();
-            value.put(SubscriptionManager.UICC_APPLICATIONS_ENABLED, true);
-            if (isSimAbsent) {
-                // When sim is absent, set the port index to invalid port index -1;
-                value.put(SubscriptionManager.PORT_INDEX, TelephonyManager.INVALID_PORT_INDEX);
-            }
-            sContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, value,
-                    SubscriptionManager.ICC_ID + "=\'" + iccId + "\'", null);
-            sInactiveIccIds[phoneId] = null;
-        }
-        sIccId[phoneId] = ICCID_STRING_FOR_NO_SIM;
-        updateSubscriptionInfoByIccId(phoneId, true /* updateEmbeddedSubs */);
-    }
-
-    protected void handleSimAbsent(int phoneId) {
-        if (!SubscriptionManager.isValidPhoneId(phoneId)) {
-            logd("handleSimAbsent on invalid phoneId");
-            return;
-        }
-        if (sIccId[phoneId] != null && !sIccId[phoneId].equals(ICCID_STRING_FOR_NO_SIM)) {
-            logd("SIM" + (phoneId + 1) + " hot plug out");
-        }
-        cleanSubscriptionInPhone(phoneId, true);
-
-        broadcastSimStateChanged(phoneId, IccCardConstants.INTENT_VALUE_ICC_ABSENT, null);
-        broadcastSimCardStateChanged(phoneId, TelephonyManager.SIM_STATE_ABSENT);
-        broadcastSimApplicationStateChanged(phoneId, TelephonyManager.SIM_STATE_UNKNOWN);
-        updateSubscriptionCarrierId(phoneId, IccCardConstants.INTENT_VALUE_ICC_ABSENT);
-        updateCarrierServices(phoneId, IccCardConstants.INTENT_VALUE_ICC_ABSENT);
-    }
-
-    protected void handleSimError(int phoneId) {
-        if (sIccId[phoneId] != null && !sIccId[phoneId].equals(ICCID_STRING_FOR_NO_SIM)) {
-            logd("SIM" + (phoneId + 1) + " Error ");
-        }
-        sIccId[phoneId] = ICCID_STRING_FOR_NO_SIM;
-        updateSubscriptionInfoByIccId(phoneId, true /* updateEmbeddedSubs */);
-        broadcastSimStateChanged(phoneId, IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR,
-                IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR);
-        broadcastSimCardStateChanged(phoneId, TelephonyManager.SIM_STATE_CARD_IO_ERROR);
-        broadcastSimApplicationStateChanged(phoneId, TelephonyManager.SIM_STATE_NOT_READY);
-        updateSubscriptionCarrierId(phoneId, IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR);
-        updateCarrierServices(phoneId, IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR);
-    }
-
-    protected synchronized void updateSubscriptionInfoByIccId(int phoneId,
-            boolean updateEmbeddedSubs) {
-        logd("updateSubscriptionInfoByIccId:+ Start - phoneId: " + phoneId);
-        if (!SubscriptionManager.isValidPhoneId(phoneId)) {
-            loge("[updateSubscriptionInfoByIccId]- invalid phoneId=" + phoneId);
-            return;
-        }
-        logd("updateSubscriptionInfoByIccId: removing subscription info record: phoneId "
-                + phoneId);
-        // Clear phoneId only when sim absent is not enough. It's possible to switch SIM profile
-        // within the same slot. Need to clear the slot index of the previous sub. Thus always clear
-        // for the changing slot first.
-        mSubscriptionController.clearSubInfoRecord(phoneId);
-
-        // If SIM is not absent, insert new record or update existing record.
-        if (!ICCID_STRING_FOR_NO_SIM.equals(sIccId[phoneId]) && sIccId[phoneId] != null) {
-            logd("updateSubscriptionInfoByIccId: adding subscription info record: iccid: "
-                    + sIccId[phoneId] + ", phoneId:" + phoneId);
-            mSubscriptionManager.addSubscriptionInfoRecord(sIccId[phoneId], phoneId);
-        }
-
-        List<SubscriptionInfo> subInfos =
-                mSubscriptionController.getSubInfoUsingSlotIndexPrivileged(phoneId);
-        if (subInfos != null) {
-            boolean changed = false;
-            for (int i = 0; i < subInfos.size(); i++) {
-                SubscriptionInfo temp = subInfos.get(i);
-                ContentValues value = new ContentValues(1);
-
-                String msisdn = TelephonyManager.getDefault().getLine1Number(
-                        temp.getSubscriptionId());
-
-                if (!TextUtils.equals(msisdn, temp.getNumber())) {
-                    value.put(SubscriptionManager.NUMBER, msisdn);
-                    sContext.getContentResolver().update(SubscriptionManager
-                            .getUriForSubscriptionId(temp.getSubscriptionId()), value, null, null);
-                    changed = true;
-                }
-            }
-            if (changed) {
-                // refresh Cached Active Subscription Info List
-                mSubscriptionController.refreshCachedActiveSubscriptionInfoList();
-            }
-        }
-
-        // TODO investigate if we can update for each slot separately.
-        if (isAllIccIdQueryDone()) {
-            // Ensure the modems are mapped correctly
-            if (mSubscriptionManager.isActiveSubId(
-                    mSubscriptionManager.getDefaultDataSubscriptionId())) {
-                mSubscriptionManager.setDefaultDataSubId(
-                        mSubscriptionManager.getDefaultDataSubscriptionId());
-            } else {
-                logd("bypass reset default data sub if inactive");
-            }
-            setSubInfoInitialized();
-        }
-
-        UiccController uiccController = UiccController.getInstance();
-        UiccSlot[] uiccSlots = uiccController.getUiccSlots();
-        if (uiccSlots != null && updateEmbeddedSubs) {
-            List<Integer> cardIds = new ArrayList<>();
-            for (UiccSlot uiccSlot : uiccSlots) {
-                if (uiccSlot != null && uiccSlot.getUiccCard() != null) {
-                    int cardId = uiccController.convertToPublicCardId(
-                            uiccSlot.getUiccCard().getCardId());
-                    cardIds.add(cardId);
-                }
-            }
-            updateEmbeddedSubscriptions(cardIds, (hasChanges) -> {
-                if (hasChanges) {
-                    mSubscriptionController.notifySubscriptionInfoChanged();
-                }
-                if (DBG) logd("updateSubscriptionInfoByIccId: SubscriptionInfo update complete");
-            });
-        }
-
-        mSubscriptionController.notifySubscriptionInfoChanged();
-        if (DBG) logd("updateSubscriptionInfoByIccId: SubscriptionInfo update complete");
-    }
-
-    private void setSubInfoInitialized() {
-        // Should only be triggered once.
-        if (!sIsSubInfoInitialized) {
-            if (DBG) logd("SubInfo Initialized");
-            sIsSubInfoInitialized = true;
-            mSubscriptionController.notifySubInfoReady();
-        }
-        MultiSimSettingController.getInstance().notifyAllSubscriptionLoaded();
-    }
-
-    /**
-     * Whether subscriptions of all SIMs are initialized.
-     */
-    public static boolean isSubInfoInitialized() {
-        return sIsSubInfoInitialized;
-    }
-
-    /**
-     * Updates the cached list of embedded subscription for the eUICC with the given list of card
-     * IDs {@code cardIds}. The step of reading the embedded subscription list from eUICC card is
-     * executed in background thread. The callback {@code callback} is executed after the cache is
-     * refreshed. The callback is executed in main thread.
-     */
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    public void updateEmbeddedSubscriptions(List<Integer> cardIds,
-            @Nullable UpdateEmbeddedSubsCallback callback) {
-        // Do nothing if eUICCs are disabled. (Previous entries may remain in the cache, but they
-        // are filtered out of list calls as long as EuiccManager.isEnabled returns false).
-        if (!mEuiccManager.isEnabled()) {
-            if (DBG) logd("updateEmbeddedSubscriptions: eUICC not enabled");
-            callback.run(false /* hasChanges */);
-            return;
-        }
-
-        mBackgroundHandler.post(() -> {
-            List<Pair<Integer, GetEuiccProfileInfoListResult>> results = new ArrayList<>();
-            for (int cardId : cardIds) {
-                GetEuiccProfileInfoListResult result =
-                        EuiccController.get().blockingGetEuiccProfileInfoList(cardId);
-                if (DBG) logd("blockingGetEuiccProfileInfoList cardId " + cardId);
-                results.add(Pair.create(cardId, result));
-            }
-
-            // The runnable will be executed in the main thread.
-            this.post(() -> {
-                boolean hasChanges = false;
-                for (Pair<Integer, GetEuiccProfileInfoListResult> cardIdAndResult : results) {
-                    if (updateEmbeddedSubscriptionsCache(cardIdAndResult.first,
-                            cardIdAndResult.second)) {
-                        hasChanges = true;
-                    }
-                }
-                // The latest state in the main thread may be changed when the callback is
-                // triggered.
-                if (callback != null) {
-                    callback.run(hasChanges);
-                }
-            });
-        });
-    }
-
-    /**
-     * Update the cached list of embedded subscription based on the passed in
-     * GetEuiccProfileInfoListResult {@code result}.
-     *
-     * @return true if changes may have been made. This is not a guarantee that changes were made,
-     * but notifications about subscription changes may be skipped if this returns false as an
-     * optimization to avoid spurious notifications.
-     */
-    private boolean updateEmbeddedSubscriptionsCache(int cardId,
-            GetEuiccProfileInfoListResult result) {
-        if (DBG) logd("updateEmbeddedSubscriptionsCache");
-
-        if (result == null) {
-            if (DBG) logd("updateEmbeddedSubscriptionsCache: IPC to the eUICC controller failed");
-            retryUpdateEmbeddedSubscriptionCards.add(cardId);
-            shouldRetryUpdateEmbeddedSubscriptions = true;
-            return false;
-        }
-
-        // If the returned result is not RESULT_OK or the profile list is null, don't update cache.
-        // Otherwise, update the cache.
-        final EuiccProfileInfo[] embeddedProfiles;
-        List<EuiccProfileInfo> list = result.getProfiles();
-        if (result.getResult() == EuiccService.RESULT_OK && list != null) {
-            embeddedProfiles = list.toArray(new EuiccProfileInfo[list.size()]);
-            if (DBG) {
-                logd("blockingGetEuiccProfileInfoList: got " + result.getProfiles().size()
-                        + " profiles");
-            }
-        } else {
-            if (DBG) {
-                logd("blockingGetEuiccProfileInfoList returns an error. "
-                        + "Result code=" + result.getResult()
-                        + ". Null profile list=" + (result.getProfiles() == null));
-            }
-            return false;
-        }
-
-        final boolean isRemovable = result.getIsRemovable();
-
-        final String[] embeddedIccids = new String[embeddedProfiles.length];
-        for (int i = 0; i < embeddedProfiles.length; i++) {
-            embeddedIccids[i] = embeddedProfiles[i].getIccid();
-        }
-
-        if (DBG) logd("Get eUICC profile list of size " + embeddedProfiles.length);
-
-        // Note that this only tracks whether we make any writes to the DB. It's possible this will
-        // be set to true for an update even when the row contents remain exactly unchanged from
-        // before, since we don't compare against the previous value. Since this is only intended to
-        // avoid some spurious broadcasts (particularly for users who don't use eSIM at all), this
-        // is fine.
-        boolean hasChanges = false;
-
-        // Update or insert records for all embedded subscriptions (except non-removable ones if the
-        // current eUICC is non-removable, since we assume these are still accessible though not
-        // returned by the eUICC controller).
-        List<SubscriptionInfo> existingSubscriptions =
-                mSubscriptionController.getSubscriptionInfoListForEmbeddedSubscriptionUpdate(
-                        embeddedIccids, isRemovable);
-        ContentResolver contentResolver = sContext.getContentResolver();
-        for (EuiccProfileInfo embeddedProfile : embeddedProfiles) {
-            int index =
-                    findSubscriptionInfoForIccid(existingSubscriptions, embeddedProfile.getIccid());
-            int prevCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
-            int nameSource = SubscriptionManager.NAME_SOURCE_CARRIER_ID;
-            if (index < 0) {
-                // No existing entry for this ICCID; create an empty one.
-                mSubscriptionController.insertEmptySubInfoRecord(
-                        embeddedProfile.getIccid(), SubscriptionManager.SIM_NOT_INSERTED);
-            } else {
-                nameSource = existingSubscriptions.get(index).getDisplayNameSource();
-                prevCarrierId = existingSubscriptions.get(index).getCarrierId();
-                existingSubscriptions.remove(index);
-            }
-
-            if (DBG) {
-                logd("embeddedProfile " + embeddedProfile + " existing record "
-                        + (index < 0 ? "not found" : "found"));
-            }
-
-            ContentValues values = new ContentValues();
-            values.put(SubscriptionManager.IS_EMBEDDED, 1);
-            List<UiccAccessRule> ruleList = embeddedProfile.getUiccAccessRules();
-            boolean isRuleListEmpty = false;
-            if (ruleList == null || ruleList.size() == 0) {
-                isRuleListEmpty = true;
-            }
-            values.put(SubscriptionManager.ACCESS_RULES,
-                    isRuleListEmpty ? null : UiccAccessRule.encodeRules(
-                            ruleList.toArray(new UiccAccessRule[ruleList.size()])));
-            values.put(SubscriptionManager.IS_REMOVABLE, isRemovable);
-            // override DISPLAY_NAME if the priority of existing nameSource is <= carrier
-            if (SubscriptionController.getNameSourcePriority(nameSource)
-                    <= SubscriptionController.getNameSourcePriority(
-                            SubscriptionManager.NAME_SOURCE_CARRIER)) {
-                values.put(SubscriptionManager.DISPLAY_NAME, embeddedProfile.getNickname());
-                values.put(SubscriptionManager.NAME_SOURCE,
-                        SubscriptionManager.NAME_SOURCE_CARRIER);
-            }
-            values.put(SubscriptionManager.PROFILE_CLASS, embeddedProfile.getProfileClass());
-            values.put(SubscriptionManager.PORT_INDEX,
-                    getEmbeddedProfilePortIndex(embeddedProfile.getIccid()));
-            CarrierIdentifier cid = embeddedProfile.getCarrierIdentifier();
-            if (cid != null) {
-                // Due to the limited subscription information, carrier id identified here might
-                // not be accurate compared with CarrierResolver. Only update carrier id if there
-                // is no valid carrier id present.
-                if (prevCarrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
-                    values.put(SubscriptionManager.CARRIER_ID,
-                            CarrierResolver.getCarrierIdFromIdentifier(sContext, cid));
-                }
-                String mcc = cid.getMcc();
-                String mnc = cid.getMnc();
-                values.put(SubscriptionManager.MCC_STRING, mcc);
-                values.put(SubscriptionManager.MCC, mcc);
-                values.put(SubscriptionManager.MNC_STRING, mnc);
-                values.put(SubscriptionManager.MNC, mnc);
-            }
-            // If cardId = unsupported or unitialized, we have no reason to update DB.
-            // Additionally, if the device does not support cardId for default eUICC, the CARD_ID
-            // field should not contain the EID
-            UiccController uiccController = UiccController.getInstance();
-            if (cardId >= 0 && uiccController.getCardIdForDefaultEuicc()
-                    != TelephonyManager.UNSUPPORTED_CARD_ID) {
-                values.put(SubscriptionManager.CARD_ID, uiccController.convertToCardString(cardId));
-            }
-            hasChanges = true;
-            contentResolver.update(SubscriptionManager.CONTENT_URI, values,
-                    SubscriptionManager.ICC_ID + "='" + embeddedProfile.getIccid() + "'", null);
-
-            // refresh Cached Active Subscription Info List
-            mSubscriptionController.refreshCachedActiveSubscriptionInfoList();
-        }
-
-        // Remove all remaining subscriptions which have embedded = true. We set embedded to false
-        // to ensure they are not returned in the list of embedded subscriptions (but keep them
-        // around in case the subscription is added back later, which is equivalent to a removable
-        // SIM being removed and reinserted).
-        if (!existingSubscriptions.isEmpty()) {
-            if (DBG) {
-                logd("Removing existing embedded subscriptions of size"
-                        + existingSubscriptions.size());
-            }
-            List<String> iccidsToRemove = new ArrayList<>();
-            for (int i = 0; i < existingSubscriptions.size(); i++) {
-                SubscriptionInfo info = existingSubscriptions.get(i);
-                if (info.isEmbedded()) {
-                    if (DBG) logd("Removing embedded subscription of IccId " + info.getIccId());
-                    iccidsToRemove.add("'" + info.getIccId() + "'");
-                }
-            }
-            String whereClause = SubscriptionManager.ICC_ID + " IN ("
-                    + TextUtils.join(",", iccidsToRemove) + ")";
-            ContentValues values = new ContentValues();
-            values.put(SubscriptionManager.IS_EMBEDDED, 0);
-            hasChanges = true;
-            contentResolver.update(SubscriptionManager.CONTENT_URI, values, whereClause, null);
-
-            // refresh Cached Active Subscription Info List
-            mSubscriptionController.refreshCachedActiveSubscriptionInfoList();
-        }
-
-        if (DBG) logd("updateEmbeddedSubscriptions done hasChanges=" + hasChanges);
-        return hasChanges;
-    }
-
-    private int getEmbeddedProfilePortIndex(String iccId) {
-        UiccSlot[] slots = UiccController.getInstance().getUiccSlots();
-        for (UiccSlot slot : slots) {
-            if (slot != null && slot.isEuicc()
-                    && slot.getPortIndexFromIccId(iccId) != TelephonyManager.INVALID_PORT_INDEX) {
-                return slot.getPortIndexFromIccId(iccId);
-            }
-        }
-        return TelephonyManager.INVALID_PORT_INDEX;
-    }
-    /**
-     * Called by CarrierConfigLoader to update the subscription before sending a broadcast.
-     */
-    public void updateSubscriptionByCarrierConfigAndNotifyComplete(int phoneId,
-            String configPackageName, PersistableBundle config, Message onComplete) {
-        post(() -> {
-            updateSubscriptionByCarrierConfig(phoneId, configPackageName, config);
-            onComplete.sendToTarget();
-        });
-    }
-
-    private String getDefaultCarrierServicePackageName() {
-        CarrierConfigManager configManager =
-                (CarrierConfigManager) sContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        return configManager.getDefaultCarrierServicePackageName();
-    }
-
-    private boolean isCarrierServicePackage(int phoneId, String pkgName) {
-        if (pkgName.equals(getDefaultCarrierServicePackageName())) return false;
-
-        String carrierPackageName = TelephonyManager.from(sContext)
-                .getCarrierServicePackageNameForLogicalSlot(phoneId);
-        if (DBG) logd("Carrier service package for subscription = " + carrierPackageName);
-        return pkgName.equals(carrierPackageName);
-    }
-
-    /**
-     * Update the currently active Subscription based on information from CarrierConfig
-     */
-    @VisibleForTesting
-    public void updateSubscriptionByCarrierConfig(
-            int phoneId, String configPackageName, PersistableBundle config) {
-        if (!SubscriptionManager.isValidPhoneId(phoneId)
-                || TextUtils.isEmpty(configPackageName) || config == null) {
-            if (DBG) {
-                logd("In updateSubscriptionByCarrierConfig(): phoneId=" + phoneId
-                        + " configPackageName=" + configPackageName + " config="
-                        + ((config == null) ? "null" : config.hashCode()));
-            }
-            return;
-        }
-
-        int currentSubId = mSubscriptionController.getSubId(phoneId);
-        if (!SubscriptionManager.isValidSubscriptionId(currentSubId)
-                || currentSubId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
-            if (DBG) logd("No subscription is active for phone being updated");
-            return;
-        }
-
-        SubscriptionInfo currentSubInfo = mSubscriptionController.getSubscriptionInfo(currentSubId);
-        if (currentSubInfo == null) {
-            loge("Couldn't retrieve subscription info for current subscription");
-            return;
-        }
-
-        ContentValues cv = new ContentValues();
-        ParcelUuid groupUuid = null;
-
-        // carrier certificates are not subscription-specific, so we want to load them even if
-        // this current package is not a CarrierServicePackage
-        String[] certs = config.getStringArray(
-            CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY);
-        UiccAccessRule[] carrierConfigAccessRules = UiccAccessRule.decodeRulesFromCarrierConfig(
-            certs);
-        cv.put(SubscriptionManager.ACCESS_RULES_FROM_CARRIER_CONFIGS,
-                UiccAccessRule.encodeRules(carrierConfigAccessRules));
-
-        if (!isCarrierServicePackage(phoneId, configPackageName)) {
-            loge("Cannot manage subId=" + currentSubId + ", carrierPackage=" + configPackageName);
-        } else {
-            boolean isOpportunistic = config.getBoolean(
-                    CarrierConfigManager.KEY_IS_OPPORTUNISTIC_SUBSCRIPTION_BOOL,
-                    currentSubInfo.isOpportunistic());
-            if (currentSubInfo.isOpportunistic() != isOpportunistic) {
-                if (DBG) logd("Set SubId=" + currentSubId + " isOpportunistic=" + isOpportunistic);
-                cv.put(SubscriptionManager.IS_OPPORTUNISTIC, isOpportunistic ? "1" : "0");
-            }
-
-            String groupUuidString =
-                config.getString(CarrierConfigManager.KEY_SUBSCRIPTION_GROUP_UUID_STRING, "");
-            if (!TextUtils.isEmpty(groupUuidString)) {
-                try {
-                    // Update via a UUID Structure to ensure consistent formatting
-                    groupUuid = ParcelUuid.fromString(groupUuidString);
-                    if (groupUuid.equals(REMOVE_GROUP_UUID)
-                            && currentSubInfo.getGroupUuid() != null) {
-                        cv.put(SubscriptionManager.GROUP_UUID, (String) null);
-                        if (DBG) logd("Group Removed for" + currentSubId);
-                    } else if (mSubscriptionController.canPackageManageGroup(
-                            groupUuid, configPackageName)) {
-                        cv.put(SubscriptionManager.GROUP_UUID, groupUuid.toString());
-                        cv.put(SubscriptionManager.GROUP_OWNER, configPackageName);
-                        if (DBG) logd("Group Added for" + currentSubId);
-                    } else {
-                        loge("configPackageName " + configPackageName + " doesn't own grouUuid "
-                            + groupUuid);
-                    }
-                } catch (IllegalArgumentException e) {
-                    loge("Invalid Group UUID=" + groupUuidString);
-                }
-            }
-        }
-
-        final int preferredUsageSetting =
-                config.getInt(
-                        CarrierConfigManager.KEY_CELLULAR_USAGE_SETTING_INT,
-                        SubscriptionManager.USAGE_SETTING_UNKNOWN);
-
-        @UsageSetting int newUsageSetting = calculateUsageSetting(
-                currentSubInfo.getUsageSetting(),
-                preferredUsageSetting);
-
-        if (newUsageSetting != currentSubInfo.getUsageSetting()) {
-            cv.put(SubscriptionManager.USAGE_SETTING, newUsageSetting);
-            if (DBG) {
-                logd("UsageSetting changed,"
-                        + " oldSetting=" + currentSubInfo.getUsageSetting()
-                        + " preferredSetting=" + preferredUsageSetting
-                        + " newSetting=" + newUsageSetting);
-            }
-        } else {
-            if (DBG) {
-                logd("UsageSetting unchanged,"
-                        + " oldSetting=" + currentSubInfo.getUsageSetting()
-                        + " preferredSetting=" + preferredUsageSetting
-                        + " newSetting=" + newUsageSetting);
-            }
-        }
-
-        if (cv.size() > 0 && sContext.getContentResolver().update(SubscriptionManager
-                    .getUriForSubscriptionId(currentSubId), cv, null, null) > 0) {
-            mSubscriptionController.refreshCachedActiveSubscriptionInfoList();
-            mSubscriptionController.notifySubscriptionInfoChanged();
-            MultiSimSettingController.getInstance().notifySubscriptionGroupChanged(groupUuid);
-        }
-    }
-
-    private static int findSubscriptionInfoForIccid(List<SubscriptionInfo> list, String iccid) {
-        for (int i = 0; i < list.size(); i++) {
-            if (TextUtils.equals(iccid, list.get(i).getIccId())) {
-                return i;
-            }
-        }
-        return -1;
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    protected void broadcastSimStateChanged(int phoneId, String state, String reason) {
-        // Note: This intent is way deprecated and is only being kept around because there's no
-        // graceful way to deprecate a sticky broadcast that has a lot of listeners.
-        // DO NOT add any new extras to this broadcast -- it is not protected by any permissions.
-        Intent i = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
-        i.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        i.putExtra(PhoneConstants.PHONE_NAME_KEY, "Phone");
-        i.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE, state);
-        i.putExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON, reason);
-        SubscriptionManager.putPhoneIdAndSubIdExtra(i, phoneId);
-        logd("Broadcasting intent ACTION_SIM_STATE_CHANGED " + state + " reason " + reason +
-                " for phone: " + phoneId);
-        IntentBroadcaster.getInstance().broadcastStickyIntent(sContext, i, phoneId);
-    }
-
-    protected void broadcastSimCardStateChanged(int phoneId, int state) {
-        if (state != sSimCardState[phoneId]) {
-            sSimCardState[phoneId] = state;
-            Intent i = new Intent(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED);
-            i.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-            i.putExtra(TelephonyManager.EXTRA_SIM_STATE, state);
-            SubscriptionManager.putPhoneIdAndSubIdExtra(i, phoneId);
-            // TODO(b/130664115) we manually populate this intent with the slotId. In the future we
-            // should do a review of whether to make this public
-            UiccSlot slot = UiccController.getInstance().getUiccSlotForPhone(phoneId);
-            int slotId = UiccController.getInstance().getSlotIdFromPhoneId(phoneId);
-            i.putExtra(PhoneConstants.SLOT_KEY, slotId);
-            if (slot != null) {
-                i.putExtra(PhoneConstants.PORT_KEY, slot.getPortIndexFromPhoneId(phoneId));
-            }
-            logd("Broadcasting intent ACTION_SIM_CARD_STATE_CHANGED "
-                    + TelephonyManager.simStateToString(state) + " for phone: " + phoneId
-                    + " slot: " + slotId + " port: " + slot.getPortIndexFromPhoneId(phoneId));
-            sContext.sendBroadcast(i, Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
-            TelephonyMetrics.getInstance().updateSimState(phoneId, state);
-        }
-    }
-
-    protected void broadcastSimApplicationStateChanged(int phoneId, int state) {
-        // Broadcast if the state has changed, except if old state was UNKNOWN and new is NOT_READY,
-        // because that's the initial state and a broadcast should be sent only on a transition
-        // after SIM is PRESENT. The only exception is eSIM boot profile, where NOT_READY is the
-        // terminal state.
-        boolean isUnknownToNotReady =
-                (sSimApplicationState[phoneId] == TelephonyManager.SIM_STATE_UNKNOWN
-                        && state == TelephonyManager.SIM_STATE_NOT_READY);
-        IccCard iccCard = PhoneFactory.getPhone(phoneId).getIccCard();
-        boolean emptyProfile = iccCard != null && iccCard.isEmptyProfile();
-        if (state != sSimApplicationState[phoneId] && (!isUnknownToNotReady || emptyProfile)) {
-            sSimApplicationState[phoneId] = state;
-            Intent i = new Intent(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED);
-            i.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-            i.putExtra(TelephonyManager.EXTRA_SIM_STATE, state);
-            SubscriptionManager.putPhoneIdAndSubIdExtra(i, phoneId);
-            // TODO(b/130664115) we populate this intent with the actual slotId. In the future we
-            // should do a review of whether to make this public
-            UiccSlot slot = UiccController.getInstance().getUiccSlotForPhone(phoneId);
-            int slotId = UiccController.getInstance().getSlotIdFromPhoneId(phoneId);
-            i.putExtra(PhoneConstants.SLOT_KEY, slotId);
-            if (slot != null) {
-                i.putExtra(PhoneConstants.PORT_KEY, slot.getPortIndexFromPhoneId(phoneId));
-            }
-            logd("Broadcasting intent ACTION_SIM_APPLICATION_STATE_CHANGED "
-                    + TelephonyManager.simStateToString(state) + " for phone: " + phoneId
-                    + " slot: " + slotId + "port: " + slot.getPortIndexFromPhoneId(phoneId));
-            sContext.sendBroadcast(i, Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
-            TelephonyMetrics.getInstance().updateSimState(phoneId, state);
-        }
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private static void logd(String message) {
-        Rlog.d(LOG_TAG, message);
-    }
-
-    private static void loge(String message) {
-        Rlog.e(LOG_TAG, message);
-    }
-
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("SubscriptionInfoUpdater:");
-        mCarrierServiceBindHelper.dump(fd, pw, args);
-    }
-}
diff --git a/src/java/com/android/internal/telephony/TelephonyAdminReceiver.java b/src/java/com/android/internal/telephony/TelephonyAdminReceiver.java
new file mode 100644
index 0000000..994405b
--- /dev/null
+++ b/src/java/com/android/internal/telephony/TelephonyAdminReceiver.java
@@ -0,0 +1,100 @@
+/*
+ * 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.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.UserManager;
+import android.telephony.Rlog;
+
+/**
+ * A BroadcastReceiver for device administration events.
+ */
+public class TelephonyAdminReceiver extends BroadcastReceiver {
+    private static final String TAG = "TelephonyAdminReceiver";
+    private final Phone mPhone;
+    // We keep track of the last value to avoid updating when unrelated user restrictions change
+    private boolean mDisallowCellular2gRestriction = false;
+    private final Context mContext;
+    private UserManager mUserManager;
+
+    public TelephonyAdminReceiver(Context context, Phone phone) {
+        mContext = context;
+        mPhone = phone;
+        mUserManager = null;
+        if (ensureUserManagerExists()) {
+            mDisallowCellular2gRestriction = mUserManager.hasUserRestriction(
+                    UserManager.DISALLOW_CELLULAR_2G);
+        }
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(UserManager.ACTION_USER_RESTRICTIONS_CHANGED);
+        context.registerReceiver(this, filter);
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Rlog.d(TAG, "Processing onReceive");
+        if (context == null || intent == null) return;
+        if (!intent.getAction().equals(UserManager.ACTION_USER_RESTRICTIONS_CHANGED)) {
+            Rlog.d(TAG, "Ignoring unexpected action: " + intent.getAction());
+            return;
+        }
+        if (!ensureUserManagerExists()) {
+            return;
+        }
+        boolean shouldDisallow2g = mUserManager.hasUserRestriction(
+                UserManager.DISALLOW_CELLULAR_2G);
+
+        if (shouldDisallow2g != mDisallowCellular2gRestriction) {
+            Rlog.i(TAG,
+                    "Updating allowed network types with new admin 2g restriction. no_cellular_2g: "
+                            + shouldDisallow2g);
+            mDisallowCellular2gRestriction = shouldDisallow2g;
+            mPhone.sendSubscriptionSettings(false);
+        } else {
+            Rlog.i(TAG, "Skipping update of allowed network types. Restriction no_cellular_2g "
+                    + "unchanged: " + mDisallowCellular2gRestriction);
+        }
+    }
+
+    /**
+     * Returns the current state of the {@link UserManager#DISALLOW_CELLULAR_2G} user restriction.
+     */
+    public boolean isCellular2gDisabled() {
+        return mDisallowCellular2gRestriction;
+    }
+
+    /**
+     * Tries to resolve the user manager system service. Returns true if successful, false
+     * otherwise.
+     */
+    private boolean ensureUserManagerExists() {
+        if (mUserManager == null) {
+            Rlog.d(TAG, "No user manager. Attempting to resolve one.");
+            mUserManager = mContext.getSystemService(UserManager.class);
+        }
+        if (mUserManager == null) {
+            Rlog.e(TAG,
+                    "Could not get a user manager instance. All operations will be no-ops until "
+                            + "one is resolved");
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
index 08c02e2..f7f24a2 100644
--- a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
+++ b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
@@ -41,6 +41,7 @@
 import com.android.internal.telephony.data.PhoneSwitcher;
 import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
+import com.android.internal.telephony.imsphone.ImsNrSaModeHandler;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
 import com.android.internal.telephony.nitz.NitzStateMachineImpl;
@@ -386,6 +387,14 @@
     }
 
     /**
+     * Create an ImsNrSaModeHandler.
+     */
+    public ImsNrSaModeHandler makeImsNrSaModeHandler(ImsPhone imsPhone) {
+
+        return new ImsNrSaModeHandler(imsPhone, imsPhone.getLooper());
+    }
+
+    /**
      * Create an AppSmsManager for per-app SMS message.
      */
     public AppSmsManager makeAppSmsManager(Context context) {
@@ -425,10 +434,6 @@
                 telephonyComponentFactory);
     }
 
-    public SubscriptionController initSubscriptionController(Context c) {
-        return SubscriptionController.init(c);
-    }
-
     public PhoneSwitcher makePhoneSwitcher(int maxDataAttachModemCount, Context context,
             Looper looper) {
         return PhoneSwitcher.make(maxDataAttachModemCount, context, looper);
@@ -441,9 +446,14 @@
         return new DisplayInfoController(phone);
     }
 
-    public MultiSimSettingController initMultiSimSettingController(Context c,
-            SubscriptionController sc) {
-        return MultiSimSettingController.init(c, sc);
+    /**
+     * Initialize multi sim settings controller.
+     *
+     * @param c The context.
+     * @return The multi sim settings controller instance.
+     */
+    public MultiSimSettingController initMultiSimSettingController(Context c) {
+        return MultiSimSettingController.init(c);
     }
 
     /**
@@ -453,11 +463,6 @@
         return new SignalStrengthController(phone);
     }
 
-    public SubscriptionInfoUpdater makeSubscriptionInfoUpdater(Looper looper, Context context,
-            SubscriptionController sc) {
-        return new SubscriptionInfoUpdater(looper, context, sc);
-    }
-
     /**
      * Create a new LinkBandwidthEstimator.
      */
diff --git a/src/java/com/android/internal/telephony/TelephonyTester.java b/src/java/com/android/internal/telephony/TelephonyTester.java
index c81a4c3..b9e04c8 100644
--- a/src/java/com/android/internal/telephony/TelephonyTester.java
+++ b/src/java/com/android/internal/telephony/TelephonyTester.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony;
 
+import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -200,11 +201,7 @@
                     sendTestSuppServiceNotification(intent);
                 } else if (action.equals(ACTION_TEST_SERVICE_STATE)) {
                     log("handle test service state changed intent");
-                    // Trigger the service state update. The replacement will be done in
-                    // overrideServiceState().
-                    mServiceStateTestIntent = intent;
-                    mPhone.getServiceStateTracker().sendEmptyMessage(
-                            ServiceStateTracker.EVENT_NETWORK_STATE_CHANGED);
+                    setServiceStateTestIntent(intent);
                 } else if (action.equals(ACTION_TEST_IMS_E_CALL)) {
                     log("handle test IMS ecall intent");
                     testImsECall();
@@ -388,6 +385,19 @@
         }
     }
 
+    /**
+     * Set the service state test intent.
+     *
+     * @param intent The service state test intent.
+     */
+    public void setServiceStateTestIntent(@NonNull Intent intent) {
+        mServiceStateTestIntent = intent;
+        // Trigger the service state update. The replacement will be done in
+        // overrideServiceState().
+        mPhone.getServiceStateTracker().sendEmptyMessage(
+                ServiceStateTracker.EVENT_NETWORK_STATE_CHANGED);
+    }
+
     void overrideServiceState(ServiceState ss) {
         if (mServiceStateTestIntent == null || ss == null) return;
         if (mPhone.getPhoneId() != mServiceStateTestIntent.getIntExtra(
@@ -401,13 +411,36 @@
         }
 
         if (mServiceStateTestIntent.hasExtra(EXTRA_VOICE_REG_STATE)) {
+            int state = mServiceStateTestIntent.getIntExtra(EXTRA_DATA_REG_STATE,
+                    ServiceState.STATE_OUT_OF_SERVICE);
             ss.setVoiceRegState(mServiceStateTestIntent.getIntExtra(EXTRA_VOICE_REG_STATE,
                     ServiceState.STATE_OUT_OF_SERVICE));
+            NetworkRegistrationInfo nri = ss.getNetworkRegistrationInfo(
+                    NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+            NetworkRegistrationInfo.Builder builder = new NetworkRegistrationInfo.Builder(nri);
+            if (state == ServiceState.STATE_IN_SERVICE) {
+                builder.setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+            } else {
+                builder.setRegistrationState(
+                        NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING);
+            }
+            ss.addNetworkRegistrationInfo(builder.build());
             log("Override voice service state with " + ss.getState());
         }
         if (mServiceStateTestIntent.hasExtra(EXTRA_DATA_REG_STATE)) {
-            ss.setDataRegState(mServiceStateTestIntent.getIntExtra(EXTRA_DATA_REG_STATE,
-                    ServiceState.STATE_OUT_OF_SERVICE));
+            int state = mServiceStateTestIntent.getIntExtra(EXTRA_DATA_REG_STATE,
+                    ServiceState.STATE_OUT_OF_SERVICE);
+            ss.setDataRegState(state);
+            NetworkRegistrationInfo nri = ss.getNetworkRegistrationInfo(
+                    NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+            NetworkRegistrationInfo.Builder builder = new NetworkRegistrationInfo.Builder(nri);
+            if (state == ServiceState.STATE_IN_SERVICE) {
+                builder.setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+            } else {
+                builder.setRegistrationState(
+                        NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING);
+            }
+            ss.addNetworkRegistrationInfo(builder.build());
             log("Override data service state with " + ss.getDataRegistrationState());
         }
         if (mServiceStateTestIntent.hasExtra(EXTRA_OPERATOR)) {
diff --git a/src/java/com/android/internal/telephony/UiccPhoneBookController.java b/src/java/com/android/internal/telephony/UiccPhoneBookController.java
index 8b8457c..96fd208 100644
--- a/src/java/com/android/internal/telephony/UiccPhoneBookController.java
+++ b/src/java/com/android/internal/telephony/UiccPhoneBookController.java
@@ -147,12 +147,7 @@
     private IccPhoneBookInterfaceManager
             getIccPhoneBookInterfaceManager(int subId) {
 
-        int phoneId;
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            phoneId = SubscriptionManagerService.getInstance().getPhoneId(subId);
-        } else {
-            phoneId = SubscriptionController.getInstance().getPhoneId(subId);
-        }
+        int phoneId = SubscriptionManagerService.getInstance().getPhoneId(subId);
         try {
             return PhoneFactory.getPhone(phoneId).getIccPhoneBookInterfaceManager();
         } catch (NullPointerException e) {
diff --git a/src/java/com/android/internal/telephony/VisualVoicemailSmsFilter.java b/src/java/com/android/internal/telephony/VisualVoicemailSmsFilter.java
index 0b10651..e374811 100644
--- a/src/java/com/android/internal/telephony/VisualVoicemailSmsFilter.java
+++ b/src/java/com/android/internal/telephony/VisualVoicemailSmsFilter.java
@@ -21,6 +21,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.os.UserHandle;
 import android.provider.VoicemailContract;
 import android.telecom.PhoneAccountHandle;
 import android.telephony.PhoneNumberUtils;
@@ -60,7 +61,7 @@
         /**
          * Convert the subId to a {@link PhoneAccountHandle}
          */
-        PhoneAccountHandle fromSubId(int subId);
+        PhoneAccountHandle fromSubId(int subId, Context context);
     }
 
     private static final String TAG = "VvmSmsFilter";
@@ -77,7 +78,7 @@
             new PhoneAccountHandleConverter() {
 
                 @Override
-                public PhoneAccountHandle fromSubId(int subId) {
+                public PhoneAccountHandle fromSubId(int subId, Context context) {
                     if (!SubscriptionManager.isValidSubscriptionId(subId)) {
                         return null;
                     }
@@ -85,6 +86,15 @@
                     if (phoneId == SubscriptionManager.INVALID_PHONE_INDEX) {
                         return null;
                     }
+                    SubscriptionManager subscriptionManager =
+                            (SubscriptionManager) context.getSystemService(
+                                Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+                    UserHandle userHandle = subscriptionManager.getSubscriptionUserHandle(subId);
+                    if (userHandle != null) {
+                        return new PhoneAccountHandle(PSTN_CONNECTION_SERVICE_COMPONENT,
+                            Integer.toString(PhoneFactory.getPhone(phoneId).getSubId()),
+                            userHandle);
+                    }
                     return new PhoneAccountHandle(PSTN_CONNECTION_SERVICE_COMPONENT,
                             Integer.toString(PhoneFactory.getPhone(phoneId).getSubId()));
                 }
@@ -138,7 +148,8 @@
             return false;
         }
 
-        PhoneAccountHandle phoneAccountHandle = sPhoneAccountHandleConverter.fromSubId(subId);
+        PhoneAccountHandle phoneAccountHandle = sPhoneAccountHandleConverter.fromSubId(subId,
+                context);
 
         if (phoneAccountHandle == null) {
             Log.e(TAG, "Unable to convert subId " + subId + " to PhoneAccountHandle");
diff --git a/src/java/com/android/internal/telephony/VoiceIndication.java b/src/java/com/android/internal/telephony/VoiceIndication.java
index 984d2a0..9720bb7 100644
--- a/src/java/com/android/internal/telephony/VoiceIndication.java
+++ b/src/java/com/android/internal/telephony/VoiceIndication.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony;
 
+import static android.telephony.TelephonyManager.HAL_SERVICE_VOICE;
+
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CALL_RING;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_CALL_WAITING;
 import static com.android.internal.telephony.RILConstants.RIL_UNSOL_CDMA_INFO_REC;
@@ -63,7 +65,7 @@
      */
     public void callRing(int indicationType, boolean isGsm,
             android.hardware.radio.voice.CdmaSignalInfoRecord record) {
-        mRil.processIndication(RIL.VOICE_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_VOICE, indicationType);
 
         char[] response = null;
 
@@ -90,7 +92,7 @@
      * @param indicationType Type of radio indication
      */
     public void callStateChanged(int indicationType) {
-        mRil.processIndication(RIL.VOICE_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_VOICE, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED);
 
@@ -104,7 +106,7 @@
      */
     public void cdmaCallWaiting(int indicationType,
             android.hardware.radio.voice.CdmaCallWaiting callWaitingRecord) {
-        mRil.processIndication(RIL.VOICE_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_VOICE, indicationType);
 
         // TODO: create a CdmaCallWaitingNotification constructor that takes in these fields to make
         // sure no fields are missing
@@ -134,7 +136,7 @@
      */
     public void cdmaInfoRec(int indicationType,
             android.hardware.radio.voice.CdmaInformationRecord[] records) {
-        mRil.processIndication(RIL.VOICE_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_VOICE, indicationType);
 
         for (int i = 0; i < records.length; i++) {
             android.hardware.radio.voice.CdmaInformationRecord record = records[i];
@@ -233,7 +235,7 @@
      * @param status CDMA OTA provision status
      */
     public void cdmaOtaProvisionStatus(int indicationType, int status) {
-        mRil.processIndication(RIL.VOICE_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_VOICE, indicationType);
 
         int[] response = new int[] {status};
 
@@ -252,7 +254,7 @@
      */
     public void currentEmergencyNumberList(int indicationType,
             android.hardware.radio.voice.EmergencyNumber[] emergencyNumberList) {
-        mRil.processIndication(RIL.VOICE_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_VOICE, indicationType);
 
         List<EmergencyNumber> response = new ArrayList<>(emergencyNumberList.length);
         for (android.hardware.radio.voice.EmergencyNumber enHal : emergencyNumberList) {
@@ -279,7 +281,7 @@
      * @param indicationType Type of radio indication
      */
     public void enterEmergencyCallbackMode(int indicationType) {
-        mRil.processIndication(RIL.VOICE_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_VOICE, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_ENTER_EMERGENCY_CALLBACK_MODE);
 
@@ -294,7 +296,7 @@
      * @param indicationType Type of radio indication
      */
     public void exitEmergencyCallbackMode(int indicationType) {
-        mRil.processIndication(RIL.VOICE_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_VOICE, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_EXIT_EMERGENCY_CALLBACK_MODE);
 
@@ -307,7 +309,7 @@
      * @param start true = start play ringback tone, false = stop playing ringback tone
      */
     public void indicateRingbackTone(int indicationType, boolean start) {
-        mRil.processIndication(RIL.VOICE_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_VOICE, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLogvRet(RIL_UNSOL_RINGBACK_TONE, start);
 
@@ -322,7 +324,7 @@
      */
     public void onSupplementaryServiceIndication(int indicationType,
             android.hardware.radio.voice.StkCcUnsolSsResult ss) {
-        mRil.processIndication(RIL.VOICE_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_VOICE, indicationType);
 
         int num;
         SsData ssData = new SsData();
@@ -374,7 +376,7 @@
      * @param msg Message string in UTF-8, if applicable
      */
     public void onUssd(int indicationType, int ussdModeType, String msg) {
-        mRil.processIndication(RIL.VOICE_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_VOICE, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLogMore(RIL_UNSOL_ON_USSD, "" + ussdModeType);
 
@@ -390,7 +392,7 @@
      * @param indicationType Type of radio indication
      */
     public void resendIncallMute(int indicationType) {
-        mRil.processIndication(RIL.VOICE_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_VOICE, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESEND_INCALL_MUTE);
 
@@ -403,7 +405,7 @@
      * @param state New SRVCC State
      */
     public void srvccStateNotify(int indicationType, int state) {
-        mRil.processIndication(RIL.VOICE_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_VOICE, indicationType);
 
         int[] response = new int[] {state};
 
@@ -419,7 +421,7 @@
      * @param alpha ALPHA string from UICC in UTF-8 format
      */
     public void stkCallControlAlphaNotify(int indicationType, String alpha) {
-        mRil.processIndication(RIL.VOICE_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_VOICE, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_STK_CC_ALPHA_NOTIFY, alpha);
 
@@ -434,7 +436,7 @@
      * @param timeout Timeout value in milliseconds for setting up voice call
      */
     public void stkCallSetup(int indicationType, long timeout) {
-        mRil.processIndication(RIL.VOICE_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_VOICE, indicationType);
 
         if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_STK_CALL_SETUP, timeout);
 
diff --git a/src/java/com/android/internal/telephony/VoiceResponse.java b/src/java/com/android/internal/telephony/VoiceResponse.java
index b1ba9d9..d1e060e 100644
--- a/src/java/com/android/internal/telephony/VoiceResponse.java
+++ b/src/java/com/android/internal/telephony/VoiceResponse.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony;
 
+import static android.telephony.TelephonyManager.HAL_SERVICE_VOICE;
+
 import android.hardware.radio.RadioError;
 import android.hardware.radio.RadioResponseInfo;
 import android.hardware.radio.voice.IRadioVoiceResponse;
@@ -47,49 +49,49 @@
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void acceptCallResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void cancelPendingUssdResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void conferenceResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void dialResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void emergencyDialResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void exitEmergencyCallbackModeResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void explicitCallTransferResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo);
     }
 
     /**
@@ -99,7 +101,7 @@
      */
     public void getCallForwardStatusResponse(RadioResponseInfo responseInfo,
             android.hardware.radio.voice.CallForwardInfo[] callForwardInfos) {
-        RILRequest rr = mRil.processResponse(RIL.VOICE_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_VOICE, responseInfo);
         if (rr != null) {
             CallForwardInfo[] ret = new CallForwardInfo[callForwardInfos.length];
             for (int i = 0; i < callForwardInfos.length; i++) {
@@ -129,7 +131,7 @@
     public void getCallWaitingResponse(RadioResponseInfo responseInfo, boolean enable,
             int serviceClass) {
         RadioResponse.responseInts(
-                RIL.VOICE_SERVICE, mRil, responseInfo, enable ? 1 : 0, serviceClass);
+                HAL_SERVICE_VOICE, mRil, responseInfo, enable ? 1 : 0, serviceClass);
     }
 
     /**
@@ -137,7 +139,7 @@
      * @param status indicates CLIP status
      */
     public void getClipResponse(RadioResponseInfo responseInfo, int status) {
-        RadioResponse.responseInts(RIL.VOICE_SERVICE, mRil, responseInfo, status);
+        RadioResponse.responseInts(HAL_SERVICE_VOICE, mRil, responseInfo, status);
     }
 
     /**
@@ -146,7 +148,7 @@
      * @param m is "m" parameter from TS 27.007 7.7
      */
     public void getClirResponse(RadioResponseInfo responseInfo, int n, int m) {
-        RadioResponse.responseInts(RIL.VOICE_SERVICE, mRil, responseInfo, n, m);
+        RadioResponse.responseInts(HAL_SERVICE_VOICE, mRil, responseInfo, n, m);
     }
 
     /**
@@ -155,7 +157,7 @@
      */
     public void getCurrentCallsResponse(RadioResponseInfo responseInfo,
             android.hardware.radio.voice.Call[] calls) {
-        RILRequest rr = mRil.processResponse(RIL.VOICE_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_VOICE, responseInfo);
 
         if (rr != null) {
             int num = calls.length;
@@ -198,7 +200,7 @@
      */
     public void getLastCallFailCauseResponse(RadioResponseInfo responseInfo,
             android.hardware.radio.voice.LastCallFailCauseInfo fcInfo) {
-        RILRequest rr = mRil.processResponse(RIL.VOICE_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_VOICE, responseInfo);
 
         if (rr != null) {
             LastCallFailCause ret = new LastCallFailCause();
@@ -216,7 +218,7 @@
      * @param enable true for "mute enabled" and false for "mute disabled"
      */
     public void getMuteResponse(RadioResponseInfo responseInfo, boolean enable) {
-        RadioResponse.responseInts(RIL.VOICE_SERVICE, mRil, responseInfo, enable ? 1 : 0);
+        RadioResponse.responseInts(HAL_SERVICE_VOICE, mRil, responseInfo, enable ? 1 : 0);
     }
 
     /**
@@ -225,7 +227,7 @@
      *        true for Enhanced Privacy Mode (Private Long Code Mask)
      */
     public void getPreferredVoicePrivacyResponse(RadioResponseInfo responseInfo, boolean enable) {
-        RadioResponse.responseInts(RIL.VOICE_SERVICE, mRil, responseInfo, enable ? 1 : 0);
+        RadioResponse.responseInts(HAL_SERVICE_VOICE, mRil, responseInfo, enable ? 1 : 0);
     }
 
     /**
@@ -233,35 +235,35 @@
      * @param mode TTY mode
      */
     public void getTtyModeResponse(RadioResponseInfo responseInfo, int mode) {
-        RadioResponse.responseInts(RIL.VOICE_SERVICE, mRil, responseInfo, mode);
+        RadioResponse.responseInts(HAL_SERVICE_VOICE, mRil, responseInfo, mode);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void handleStkCallSetupRequestFromSimResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void hangupConnectionResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void hangupForegroundResumeBackgroundResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void hangupWaitingOrBackgroundResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo);
     }
 
     /**
@@ -269,7 +271,7 @@
      * @param enable true for "vonr enabled" and false for "vonr disabled"
      */
     public void isVoNrEnabledResponse(RadioResponseInfo responseInfo, boolean enable) {
-        RILRequest rr = mRil.processResponse(RIL.VOICE_SERVICE, responseInfo);
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_VOICE, responseInfo);
 
         if (rr != null) {
             if (responseInfo.error == RadioError.NONE) {
@@ -283,112 +285,112 @@
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void rejectCallResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void sendBurstDtmfResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void sendCdmaFeatureCodeResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void sendDtmfResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void sendUssdResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void separateConnectionResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setCallForwardResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setCallWaitingResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setClirResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setMuteResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setPreferredVoicePrivacyResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setTtyModeResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setVoNrEnabledResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void startDtmfResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void stopDtmfResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo);
     }
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void switchWaitingOrHoldingAndActiveResponse(RadioResponseInfo responseInfo) {
-        RadioResponse.responseVoid(RIL.VOICE_SERVICE, mRil, responseInfo);
+        RadioResponse.responseVoid(HAL_SERVICE_VOICE, mRil, responseInfo);
     }
 
     @Override
diff --git a/src/java/com/android/internal/telephony/WapPushOverSms.java b/src/java/com/android/internal/telephony/WapPushOverSms.java
old mode 100755
new mode 100644
index 08c7acd..d6ea4ad
--- a/src/java/com/android/internal/telephony/WapPushOverSms.java
+++ b/src/java/com/android/internal/telephony/WapPushOverSms.java
@@ -46,6 +46,7 @@
 import android.text.TextUtils;
 
 import com.android.internal.telephony.uicc.IccUtils;
+import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.telephony.Rlog;
 
 import com.google.android.mms.pdu.GenericPdu;
@@ -392,7 +393,10 @@
 
         // Direct the intent to only the default MMS app. If we can't find a default MMS app
         // then sent it to all broadcast receivers.
-        ComponentName componentName = SmsApplication.getDefaultMmsApplication(mContext, true);
+        UserHandle userHandle = TelephonyUtils.getSubscriptionUserHandle(mContext, subId);
+        ComponentName componentName = SmsApplication.getDefaultMmsApplicationAsUser(mContext,
+                true, userHandle);
+
         Bundle options = null;
         if (componentName != null) {
             // Deliver MMS message only to this receiver
@@ -410,9 +414,12 @@
             options = bopts.toBundle();
         }
 
+        if (userHandle == null) {
+            userHandle = UserHandle.SYSTEM;
+        }
         handler.dispatchIntent(intent, getPermissionForType(result.mimeType),
                 getAppOpsStringPermissionForIntent(result.mimeType), options, receiver,
-                UserHandle.SYSTEM, subId);
+                userHandle, subId);
         return Activity.RESULT_OK;
     }
 
diff --git a/src/java/com/android/internal/telephony/WspTypeDecoder.java b/src/java/com/android/internal/telephony/WspTypeDecoder.java
old mode 100755
new mode 100644
diff --git a/src/java/com/android/internal/telephony/cat/AppInterface.java b/src/java/com/android/internal/telephony/cat/AppInterface.java
old mode 100755
new mode 100644
diff --git a/src/java/com/android/internal/telephony/cat/CatCmdMessage.java b/src/java/com/android/internal/telephony/cat/CatCmdMessage.java
index 3d21270..4447c07 100644
--- a/src/java/com/android/internal/telephony/cat/CatCmdMessage.java
+++ b/src/java/com/android/internal/telephony/cat/CatCmdMessage.java
@@ -40,6 +40,7 @@
     private ToneSettings mToneSettings = null;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private CallSettings mCallSettings = null;
+    private SMSSettings mSMSSettings =  null;
     private SetupEventListSettings mSetupEventListSettings = null;
     private boolean mLoadIconFailed = false;
 
@@ -61,6 +62,14 @@
         public TextMessage callMsg;
     }
 
+    /**
+     * Container for SEND SMS  command settings.
+     */
+    public class SMSSettings {
+        public TextMessage smsText;
+        public TextMessage destAddr;
+    }
+
     public class SetupEventListSettings {
         @UnsupportedAppUsage
         public int[] eventList;
@@ -84,57 +93,69 @@
         mCmdDet = cmdParams.mCmdDet;
         mLoadIconFailed =  cmdParams.mLoadIconFailed;
         switch(getCmdType()) {
-        case SET_UP_MENU:
-        case SELECT_ITEM:
-            mMenu = ((SelectItemParams) cmdParams).mMenu;
-            break;
-        case DISPLAY_TEXT:
-        case SET_UP_IDLE_MODE_TEXT:
-        case SEND_DTMF:
-        case SEND_SMS:
-        case REFRESH:
-        case RUN_AT:
-        case SEND_SS:
-        case SEND_USSD:
-            mTextMsg = ((DisplayTextParams) cmdParams).mTextMsg;
-            break;
-        case GET_INPUT:
-        case GET_INKEY:
-            mInput = ((GetInputParams) cmdParams).mInput;
-            break;
-        case LAUNCH_BROWSER:
-            mTextMsg = ((LaunchBrowserParams) cmdParams).mConfirmMsg;
-            mBrowserSettings = new BrowserSettings();
-            mBrowserSettings.url = ((LaunchBrowserParams) cmdParams).mUrl;
-            mBrowserSettings.mode = ((LaunchBrowserParams) cmdParams).mMode;
-            break;
-        case PLAY_TONE:
-            PlayToneParams params = (PlayToneParams) cmdParams;
-            mToneSettings = params.mSettings;
-            mTextMsg = params.mTextMsg;
-            break;
-        case GET_CHANNEL_STATUS:
-            mTextMsg = ((CallSetupParams) cmdParams).mConfirmMsg;
-            break;
-        case SET_UP_CALL:
-            mCallSettings = new CallSettings();
-            mCallSettings.confirmMsg = ((CallSetupParams) cmdParams).mConfirmMsg;
-            mCallSettings.callMsg = ((CallSetupParams) cmdParams).mCallMsg;
-            break;
-        case OPEN_CHANNEL:
-        case CLOSE_CHANNEL:
-        case RECEIVE_DATA:
-        case SEND_DATA:
-            BIPClientParams param = (BIPClientParams) cmdParams;
-            mTextMsg = param.mTextMsg;
-            break;
-        case SET_UP_EVENT_LIST:
-            mSetupEventListSettings = new SetupEventListSettings();
-            mSetupEventListSettings.eventList = ((SetEventListParams) cmdParams).mEventInfo;
-            break;
-        case PROVIDE_LOCAL_INFORMATION:
-        default:
-            break;
+            case SET_UP_MENU:
+            case SELECT_ITEM:
+                mMenu = ((SelectItemParams) cmdParams).mMenu;
+                break;
+            case SEND_SMS:
+                /* If cmdParams  is an instanceof SendSMSParams , then it means config value
+                 * config_stk_sms_send_support is true and the SMS should be sent by framework
+                 */
+                if (cmdParams instanceof SendSMSParams) {
+                    mSMSSettings = new SMSSettings();
+                    mSMSSettings.smsText = ((SendSMSParams) cmdParams).mTextSmsMsg;
+                    mSMSSettings.destAddr = ((SendSMSParams) cmdParams).mDestAddress;
+                    mTextMsg = ((SendSMSParams) cmdParams).mDisplayText.mTextMsg;
+                } else {
+                    mTextMsg = ((DisplayTextParams) cmdParams).mTextMsg;
+                }
+                break;
+            case DISPLAY_TEXT:
+            case SET_UP_IDLE_MODE_TEXT:
+            case SEND_DTMF:
+            case REFRESH:
+            case RUN_AT:
+            case SEND_SS:
+            case SEND_USSD:
+                mTextMsg = ((DisplayTextParams) cmdParams).mTextMsg;
+                break;
+            case GET_INPUT:
+            case GET_INKEY:
+                mInput = ((GetInputParams) cmdParams).mInput;
+                break;
+            case LAUNCH_BROWSER:
+                mTextMsg = ((LaunchBrowserParams) cmdParams).mConfirmMsg;
+                mBrowserSettings = new BrowserSettings();
+                mBrowserSettings.url = ((LaunchBrowserParams) cmdParams).mUrl;
+                mBrowserSettings.mode = ((LaunchBrowserParams) cmdParams).mMode;
+                break;
+            case PLAY_TONE:
+                PlayToneParams params = (PlayToneParams) cmdParams;
+                mToneSettings = params.mSettings;
+                mTextMsg = params.mTextMsg;
+                break;
+            case GET_CHANNEL_STATUS:
+                mTextMsg = ((CallSetupParams) cmdParams).mConfirmMsg;
+                break;
+            case SET_UP_CALL:
+                mCallSettings = new CallSettings();
+                mCallSettings.confirmMsg = ((CallSetupParams) cmdParams).mConfirmMsg;
+                mCallSettings.callMsg = ((CallSetupParams) cmdParams).mCallMsg;
+                break;
+            case OPEN_CHANNEL:
+            case CLOSE_CHANNEL:
+            case RECEIVE_DATA:
+            case SEND_DATA:
+                BIPClientParams param = (BIPClientParams) cmdParams;
+                mTextMsg = param.mTextMsg;
+                break;
+            case SET_UP_EVENT_LIST:
+                mSetupEventListSettings = new SetupEventListSettings();
+                mSetupEventListSettings.eventList = ((SetEventListParams) cmdParams).mEventInfo;
+                break;
+            case PROVIDE_LOCAL_INFORMATION:
+            default:
+                break;
         }
     }
 
@@ -145,29 +166,34 @@
         mInput = in.readParcelable(Input.class.getClassLoader());
         mLoadIconFailed = (in.readByte() == 1);
         switch (getCmdType()) {
-        case LAUNCH_BROWSER:
-            mBrowserSettings = new BrowserSettings();
-            mBrowserSettings.url = in.readString();
-            mBrowserSettings.mode = LaunchBrowserMode.values()[in.readInt()];
-            break;
-        case PLAY_TONE:
-            mToneSettings = in.readParcelable(ToneSettings.class.getClassLoader());
-            break;
-        case SET_UP_CALL:
-            mCallSettings = new CallSettings();
-            mCallSettings.confirmMsg = in.readParcelable(TextMessage.class.getClassLoader());
-            mCallSettings.callMsg = in.readParcelable(TextMessage.class.getClassLoader());
-            break;
-        case SET_UP_EVENT_LIST:
-            mSetupEventListSettings = new SetupEventListSettings();
-            int length = in.readInt();
-            mSetupEventListSettings.eventList = new int[length];
-            for (int i = 0; i < length; i++) {
-                mSetupEventListSettings.eventList[i] = in.readInt();
-            }
-            break;
-        default:
-            break;
+            case LAUNCH_BROWSER:
+                mBrowserSettings = new BrowserSettings();
+                mBrowserSettings.url = in.readString();
+                mBrowserSettings.mode = LaunchBrowserMode.values()[in.readInt()];
+                break;
+            case PLAY_TONE:
+                mToneSettings = in.readParcelable(ToneSettings.class.getClassLoader());
+                break;
+            case SET_UP_CALL:
+                mCallSettings = new CallSettings();
+                mCallSettings.confirmMsg = in.readParcelable(TextMessage.class.getClassLoader());
+                mCallSettings.callMsg = in.readParcelable(TextMessage.class.getClassLoader());
+                break;
+            case SET_UP_EVENT_LIST:
+                mSetupEventListSettings = new SetupEventListSettings();
+                int length = in.readInt();
+                mSetupEventListSettings.eventList = new int[length];
+                for (int i = 0; i < length; i++) {
+                    mSetupEventListSettings.eventList[i] = in.readInt();
+                }
+                break;
+            case SEND_SMS:
+                mSMSSettings = new SMSSettings();
+                mSMSSettings.smsText = in.readParcelable(SendSMSParams.class.getClassLoader());
+                mSMSSettings.destAddr = in.readParcelable(SendSMSParams.class.getClassLoader());
+                break;
+            default:
+                break;
         }
     }
 
@@ -178,23 +204,29 @@
         dest.writeParcelable(mMenu, 0);
         dest.writeParcelable(mInput, 0);
         dest.writeByte((byte) (mLoadIconFailed ? 1 : 0));
-        switch(getCmdType()) {
-        case LAUNCH_BROWSER:
-            dest.writeString(mBrowserSettings.url);
-            dest.writeInt(mBrowserSettings.mode.ordinal());
-            break;
-        case PLAY_TONE:
-            dest.writeParcelable(mToneSettings, 0);
-            break;
-        case SET_UP_CALL:
-            dest.writeParcelable(mCallSettings.confirmMsg, 0);
-            dest.writeParcelable(mCallSettings.callMsg, 0);
-            break;
-        case SET_UP_EVENT_LIST:
-            dest.writeIntArray(mSetupEventListSettings.eventList);
-            break;
-        default:
-            break;
+        switch (getCmdType()) {
+            case LAUNCH_BROWSER:
+                dest.writeString(mBrowserSettings.url);
+                dest.writeInt(mBrowserSettings.mode.ordinal());
+                break;
+            case PLAY_TONE:
+                dest.writeParcelable(mToneSettings, 0);
+                break;
+            case SET_UP_CALL:
+                dest.writeParcelable(mCallSettings.confirmMsg, 0);
+                dest.writeParcelable(mCallSettings.callMsg, 0);
+                break;
+            case SET_UP_EVENT_LIST:
+                dest.writeIntArray(mSetupEventListSettings.eventList);
+                break;
+            case SEND_SMS:
+                if (mSMSSettings != null) {
+                    dest.writeParcelable(mSMSSettings.smsText, 0);
+                    dest.writeParcelable(mSMSSettings.destAddr, 0);
+                }
+                break;
+            default:
+                break;
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/cat/CatService.java b/src/java/com/android/internal/telephony/cat/CatService.java
index 4798a97..fa2b19b 100644
--- a/src/java/com/android/internal/telephony/cat/CatService.java
+++ b/src/java/com/android/internal/telephony/cat/CatService.java
@@ -20,26 +20,35 @@
 import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants.LANGUAGE_SELECTION_EVENT;
 import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants.USER_ACTIVITY_EVENT;
 
+import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.PendingIntent;
 import android.app.backup.BackupManager;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources.NotFoundException;
 import android.os.AsyncResult;
 import android.os.Build;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.LocaleList;
+import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.PhoneFactory;
-import com.android.internal.telephony.SubscriptionController;
+import com.android.internal.telephony.ProxyController;
+import com.android.internal.telephony.SmsController;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.uicc.IccCardStatus.CardState;
 import com.android.internal.telephony.uicc.IccFileHandler;
@@ -140,12 +149,21 @@
 
     static final String STK_DEFAULT = "Default Message";
 
+    private static final String SMS_DELIVERY_ACTION =
+            "com.android.internal.telephony.cat.SMS_DELIVERY_ACTION";
+    private static final String SMS_SENT_ACTION =
+            "com.android.internal.telephony.cat.SMS_SENT_ACTION";
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private int mSlotId;
+    private static HandlerThread sCatServiceThread;
 
     /* For multisim catservice should not be singleton */
     private CatService(CommandsInterface ci, UiccCardApplication ca, IccRecords ir,
-            Context context, IccFileHandler fh, UiccProfile uiccProfile, int slotId) {
+            Context context, IccFileHandler fh, UiccProfile uiccProfile, int slotId,
+            Looper looper) {
+        //creating new thread to avoid deadlock conditions with the framework thread.
+        super(looper);
         if (ci == null || ca == null || ir == null || context == null || fh == null
                 || uiccProfile == null) {
             throw new NullPointerException(
@@ -189,6 +207,9 @@
 
         CatLog.d(this, "Running CAT service on Slotid: " + mSlotId +
                 ". STK app installed:" + mStkAppInstalled);
+
+        mContext.registerReceiver(mSmsBroadcastReceiver, new IntentFilter(SMS_DELIVERY_ACTION));
+        mContext.registerReceiver(mSmsBroadcastReceiver, new IntentFilter(SMS_SENT_ACTION));
     }
 
     /**
@@ -202,6 +223,10 @@
      */
     public static CatService getInstance(CommandsInterface ci,
             Context context, UiccProfile uiccProfile, int slotId) {
+        if (sCatServiceThread == null) {
+            sCatServiceThread = new HandlerThread("CatServiceThread");
+            sCatServiceThread.start();
+        }
         UiccCardApplication ca = null;
         IccFileHandler fh = null;
         IccRecords ir = null;
@@ -229,8 +254,8 @@
                         || uiccProfile == null) {
                     return null;
                 }
-
-                sInstance[slotId] = new CatService(ci, ca, ir, context, fh, uiccProfile, slotId);
+                sInstance[slotId] = new CatService(ci, ca, ir, context, fh, uiccProfile, slotId,
+                        sCatServiceThread.getLooper());
             } else if ((ir != null) && (mIccRecords != ir)) {
                 if (mIccRecords != null) {
                     mIccRecords.unregisterForRecordsLoaded(sInstance[slotId]);
@@ -449,8 +474,49 @@
                     ((DisplayTextParams)cmdParams).mTextMsg.text = null;
                 }
                 break;
-            case SEND_DTMF:
             case SEND_SMS:
+                /* If cmdParams  is an instanceof SendSMSParams , then it means config value
+                 * config_stk_sms_send_support is true and the SMS should be sent by framework
+                 */
+                if (cmdParams instanceof SendSMSParams) {
+                    String text = null, destAddr = null;
+                    if (((SendSMSParams) cmdParams).mTextSmsMsg != null) {
+                        text = ((SendSMSParams) cmdParams).mTextSmsMsg.text;
+                    }
+                    if (((SendSMSParams) cmdParams).mDestAddress != null) {
+                        destAddr = ((SendSMSParams) cmdParams).mDestAddress.text;
+                    }
+                    if (text != null && destAddr != null) {
+                        ProxyController proxyController = ProxyController.getInstance(mContext);
+                        SubscriptionManager subscriptionManager = (SubscriptionManager)
+                                mContext.getSystemService(
+                                        Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+                        SubscriptionInfo subInfo =
+                                subscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(
+                                        mSlotId);
+                        if (subInfo != null) {
+                            sendStkSms(text, destAddr, subInfo.getSubscriptionId(), cmdParams,
+                                    proxyController);
+                        } else {
+                            sendTerminalResponse(cmdParams.mCmdDet,
+                                    ResultCode.CMD_DATA_NOT_UNDERSTOOD, false, 0x00, null);
+                            CatLog.d(this, "Subscription info is null");
+                        }
+                    } else {
+                        sendTerminalResponse(cmdParams.mCmdDet, ResultCode.CMD_DATA_NOT_UNDERSTOOD,
+                                false, 0x00, null);
+                        CatLog.d(this, "Sms text or Destination Address is null");
+                    }
+                } else {
+                    if ((((DisplayTextParams) cmdParams).mTextMsg.text != null)
+                            && (((DisplayTextParams) cmdParams).mTextMsg.text.equals(
+                            STK_DEFAULT))) {
+                        message = mContext.getText(com.android.internal.R.string.sending);
+                        ((DisplayTextParams) cmdParams).mTextMsg.text = message.toString();
+                    }
+                }
+                break;
+            case SEND_DTMF:
             case SEND_SS:
             case SEND_USSD:
                 if ((((DisplayTextParams)cmdParams).mTextMsg.text != null)
@@ -538,6 +604,94 @@
         broadcastCatCmdIntent(cmdMsg);
     }
 
+    /**
+     * Used to send STK based sms via CATService
+     * @param text The message body
+     * @param destAddr The destination Address
+     * @param subId Subscription Id
+     * @param cmdParams Send SMS Command Params
+     * @param proxyController ProxyController
+     * @hide
+     */
+    public void sendStkSms(String text, String destAddr, int subId, CommandParams cmdParams,
+            ProxyController proxyController) {
+        PendingIntent sentPendingIntent = PendingIntent.getBroadcast(mContext, 0,
+                new Intent(SMS_SENT_ACTION)
+                        .putExtra("cmdDetails", cmdParams.mCmdDet)
+                        .setPackage(mContext.getPackageName()),
+                PendingIntent.FLAG_MUTABLE);
+        PendingIntent deliveryPendingIntent = PendingIntent.getBroadcast(mContext, 0,
+                new Intent(SMS_DELIVERY_ACTION)
+                        .putExtra("cmdDetails", cmdParams.mCmdDet)
+                        .setPackage(mContext.getPackageName()),
+                PendingIntent.FLAG_MUTABLE);
+        SmsController smsController = proxyController.getSmsController();
+        smsController.sendTextForSubscriber(subId, mContext.getOpPackageName(),
+                mContext.getAttributionTag(), destAddr, null, text, sentPendingIntent,
+                deliveryPendingIntent, false, 0L, true, true);
+    }
+
+    /**
+     * BroadcastReceiver class to handle error and success cases of
+     * SEND and DELIVERY pending intents used for sending of STK SMS
+     */
+    @VisibleForTesting
+    public final BroadcastReceiver mSmsBroadcastReceiver = new BroadcastReceiver() {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            CommandDetails commandDetails = (CommandDetails) intent.getExtra("cmdDetails");
+            if (intent.getAction().equals(SMS_SENT_ACTION)) {
+                int resultCode = getResultCode();
+                ResultCode terminalResponseResultCode = ResultCode.NETWORK_CRNTLY_UNABLE_TO_PROCESS;
+                CatLog.d(this, "STK SMS errorCode : " + resultCode);
+                int additionalInfo = 0;
+                if (resultCode != Activity.RESULT_OK) {
+                    /**
+                     * The Terminal Response Result code is assigned as per Section 12.12.3
+                     * and 12.12.5 TS 101.267. The Result code SMS_RP_ERROR is added in Ims Case
+                     * and additional information is added as per RP-Cause Values in TS 124.011.
+                     * The Result code NETWORK_CRNTLY_UNABLE_TO_PROCESS is added in non-Ims Case
+                     * and additional information added as per cause values in TS 04.08.
+                     */
+                    if (intent.hasExtra("ims") && intent.getBooleanExtra("ims", false)) {
+                        terminalResponseResultCode = ResultCode.SMS_RP_ERROR;
+                        //Additional information's 8th bit is 0 as per section 12.12.5 of TS 101.267
+                        if (intent.hasExtra("errorCode")) {
+                            additionalInfo = (int) intent.getExtra("errorCode");
+                            if ((additionalInfo & 0x80) != 0) additionalInfo = 0;
+                        }
+                    } else {
+                        //Additional information's 8th bit is 1 as per section 12.12.3 of TS 101.267
+                        if (intent.hasExtra("errorCode")) {
+                            additionalInfo = (int) intent.getExtra("errorCode");
+                            additionalInfo |= 0x80;
+                        }
+                    }
+                    CatLog.d(this, "Error delivering STK SMS errorCode : " + additionalInfo
+                            + " terminalResponseResultCode = " + terminalResponseResultCode);
+                    sendTerminalResponse(commandDetails, terminalResponseResultCode,
+                            true, additionalInfo, null);
+                } else {
+                    CatLog.d(this, " STK SMS sent successfully ");
+                }
+            }
+            if (intent.getAction().equals(SMS_DELIVERY_ACTION)) {
+                int resultCode = getResultCode();
+                switch (resultCode) {
+                    case Activity.RESULT_OK:
+                        sendTerminalResponse(commandDetails, ResultCode.OK, false, 0, null);
+                        CatLog.d(this, " STK SMS delivered successfully ");
+                        break;
+                    default:
+                        CatLog.d(this, "Error delivering STK SMS : " + resultCode);
+                        sendTerminalResponse(commandDetails,
+                                ResultCode.TERMINAL_CRNTLY_UNABLE_TO_PROCESS, false,
+                                0, null);
+                }
+            }
+        }
+    };
 
     private void broadcastCatCmdIntent(CatCmdMessage cmdMsg) {
         Intent intent = new Intent(AppInterface.CAT_CMD_ACTION);
@@ -810,16 +964,9 @@
     //TODO Need to take care for MSIM
     public static AppInterface getInstance() {
         int slotId = PhoneConstants.DEFAULT_SLOT_INDEX;
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            if (SubscriptionManagerService.getInstance() != null) {
-                slotId = SubscriptionManagerService.getInstance().getSlotIndex(
-                        SubscriptionManagerService.getInstance().getDefaultSubId());
-            }
-        } else {
-            SubscriptionController sControl = SubscriptionController.getInstance();
-            if (sControl != null) {
-                slotId = sControl.getSlotIndex(sControl.getDefaultSubId());
-            }
+        if (SubscriptionManagerService.getInstance() != null) {
+            slotId = SubscriptionManagerService.getInstance().getSlotIndex(
+                    SubscriptionManagerService.getInstance().getDefaultSubId());
         }
         return getInstance(null, null, null, slotId);
     }
@@ -854,7 +1001,11 @@
                     }
                 }
             }
-            mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, data));
+            if (mMsgDecoder != null) {
+                mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, data));
+            } else {
+                CatLog.e(this, "Error in handleMessage (" + msg.what + ") mMsgDecoder is NULL");
+            }
             break;
         case MSG_ID_CALL_SETUP:
             mMsgDecoder.sendStartDecodingMessageParams(new RilMessage(msg.what, null));
diff --git a/src/java/com/android/internal/telephony/cat/CommandParams.java b/src/java/com/android/internal/telephony/cat/CommandParams.java
old mode 100755
new mode 100644
index b9de4d1..8530ee2
--- a/src/java/com/android/internal/telephony/cat/CommandParams.java
+++ b/src/java/com/android/internal/telephony/cat/CommandParams.java
@@ -22,16 +22,16 @@
 
 /**
  * Container class for proactive command parameters.
- *
+ * @hide
  */
-class CommandParams {
+public class CommandParams {
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     CommandDetails mCmdDet;
     // Variable to track if an optional icon load has failed.
     boolean mLoadIconFailed = false;
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    CommandParams(CommandDetails cmdDet) {
+    public CommandParams(CommandDetails cmdDet) {
         mCmdDet = cmdDet;
     }
 
diff --git a/src/java/com/android/internal/telephony/cat/CommandParamsFactory.java b/src/java/com/android/internal/telephony/cat/CommandParamsFactory.java
index 7fbebfa..65f3c4a 100644
--- a/src/java/com/android/internal/telephony/cat/CommandParamsFactory.java
+++ b/src/java/com/android/internal/telephony/cat/CommandParamsFactory.java
@@ -29,6 +29,7 @@
 import android.os.Build;
 import android.os.Handler;
 import android.os.Message;
+import android.telephony.SmsMessage;
 import android.text.TextUtils;
 
 import com.android.internal.telephony.GsmAlphabet;
@@ -40,9 +41,9 @@
 /**
  * Factory class, used for decoding raw byte arrays, received from baseband,
  * into a CommandParams object.
- *
+ * @hide
  */
-class CommandParamsFactory extends Handler {
+public class CommandParamsFactory extends Handler {
     private static CommandParamsFactory sInstance = null;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private IconLoader mIconLoader;
@@ -53,6 +54,7 @@
     private String mSavedLanguage;
     private String mRequestedLanguage;
     private boolean mNoAlphaUsrCnf = false;
+    private boolean mStkSmsSendViaTelephony = false;
 
     // constants
     static final int MSG_ID_LOAD_ICON_DONE = 1;
@@ -86,7 +88,15 @@
     private static final int MAX_GSM7_DEFAULT_CHARS = 239;
     private static final int MAX_UCS2_CHARS = 118;
 
-    static synchronized CommandParamsFactory getInstance(RilMessageDecoder caller,
+    /**
+     * Returns a singleton instance of CommandParamsFactory
+     * @param caller Class used for queuing raw ril messages, decoding them into
+     *               CommandParams objects and sending the result back to the CAT Service.
+     * @param fh IccFileHandler Object
+     * @param context The Context
+     * @return CommandParamsFactory instance
+     */
+    public static synchronized CommandParamsFactory getInstance(RilMessageDecoder caller,
             IccFileHandler fh, Context context) {
         if (sInstance != null) {
             return sInstance;
@@ -106,6 +116,12 @@
         } catch (NotFoundException e) {
             mNoAlphaUsrCnf = false;
         }
+        try {
+            mStkSmsSendViaTelephony = context.getResources().getBoolean(
+                    com.android.internal.R.bool.config_stk_sms_send_support);
+        } catch (NotFoundException e) {
+            mStkSmsSendViaTelephony = false;
+        }
     }
 
     private CommandDetails processCommandDetails(List<ComprehensionTlv> ctlvs) {
@@ -187,8 +203,14 @@
                 case GET_INPUT:
                     cmdPending = processGetInput(cmdDet, ctlvs);
                     break;
-                case SEND_DTMF:
                 case SEND_SMS:
+                    if (mStkSmsSendViaTelephony) {
+                        cmdPending = processSMSEventNotify(cmdDet, ctlvs);
+                    } else {
+                        cmdPending = processEventNotify(cmdDet, ctlvs);
+                    }
+                    break;
+                case SEND_DTMF:
                 case REFRESH:
                 case RUN_AT:
                 case SEND_SS:
@@ -735,6 +757,62 @@
         return false;
     }
 
+
+    /**
+     * Processes SMS_EVENT_NOTIFY message from baseband.
+     *
+     * Method extracts values such as Alpha Id,Icon Id,Sms Tpdu etc from the ComprehensionTlv,
+     * in order to create the CommandParams i.e. SendSMSParams.
+     *
+     * @param cmdDet Command Details container object.
+     * @param ctlvs List of ComprehensionTlv objects following Command Details
+     *        object and Device Identities object within the proactive command
+     * @return true if the command is processing is pending and additional
+     *         asynchronous processing is required.
+     * @hide
+     */
+    public boolean processSMSEventNotify(CommandDetails cmdDet,
+            List<ComprehensionTlv> ctlvs) throws ResultException {
+        CatLog.d(this, "processSMSEventNotify");
+
+        TextMessage textMsg = new TextMessage();
+        IconId iconId = null;
+
+        ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.ALPHA_ID,
+                ctlvs);
+        /* Retrieves alpha identifier from an Alpha Identifier COMPREHENSION-TLV object.
+         *
+         * String corresponding to the alpha identifier is obtained and saved as part of
+         * the DisplayTextParams.
+         */
+        textMsg.text = ValueParser.retrieveAlphaId(ctlv, mNoAlphaUsrCnf);
+
+        ctlv = searchForTag(ComprehensionTlvTag.ICON_ID, ctlvs);
+        if (ctlv != null) {
+            // Retrieves icon id from the Icon Identifier COMPREHENSION-TLV object
+            iconId = ValueParser.retrieveIconId(ctlv);
+            textMsg.iconSelfExplanatory = iconId.selfExplanatory;
+        }
+
+        textMsg.responseNeeded = false;
+        DisplayTextParams displayTextParams = new DisplayTextParams(cmdDet, textMsg);
+        ComprehensionTlv ctlvTpdu = searchForTag(ComprehensionTlvTag.SMS_TPDU,
+                ctlvs);
+        // Retrieves smsMessage from the SMS TPDU COMPREHENSION-TLV object
+        SmsMessage smsMessage = ValueParser.retrieveTpduAsSmsMessage(ctlvTpdu);
+        if (smsMessage != null) {
+            TextMessage smsText = new TextMessage();
+            // Obtains the sms message content.
+            smsText.text = smsMessage.getMessageBody();
+            TextMessage destAddr = new TextMessage();
+            // Obtains the destination Address.
+            destAddr.text = smsMessage.getRecipientAddress();
+            mCmdParams = new SendSMSParams(cmdDet, smsText, destAddr, displayTextParams);
+            return false;
+        }
+        return true;
+    }
+
     /**
      * Processes SET_UP_EVENT_LIST proactive command from the SIM card.
      *
diff --git a/src/java/com/android/internal/telephony/cat/ComprehensionTlv.java b/src/java/com/android/internal/telephony/cat/ComprehensionTlv.java
index 5542b65..416c669 100644
--- a/src/java/com/android/internal/telephony/cat/ComprehensionTlv.java
+++ b/src/java/com/android/internal/telephony/cat/ComprehensionTlv.java
@@ -50,7 +50,7 @@
      * @param data Byte array containing the value
      * @param valueIndex Index in data at which the value starts
      */
-    protected ComprehensionTlv(int tag, boolean cr, int length, byte[] data,
+    public ComprehensionTlv(int tag, boolean cr, int length, byte[] data,
             int valueIndex) {
         mTag = tag;
         mCr = cr;
diff --git a/src/java/com/android/internal/telephony/cat/RilMessageDecoder.java b/src/java/com/android/internal/telephony/cat/RilMessageDecoder.java
old mode 100755
new mode 100644
index c25b59e..4b10cae
--- a/src/java/com/android/internal/telephony/cat/RilMessageDecoder.java
+++ b/src/java/com/android/internal/telephony/cat/RilMessageDecoder.java
@@ -32,8 +32,9 @@
 /**
  * Class used for queuing raw ril messages, decoding them into CommanParams
  * objects and sending the result back to the CAT Service.
+ * @hide
  */
-class RilMessageDecoder extends StateMachine {
+public class RilMessageDecoder extends StateMachine {
 
     // constants
     private static final int CMD_START = 1;
diff --git a/src/java/com/android/internal/telephony/cat/SendSMSParams.java b/src/java/com/android/internal/telephony/cat/SendSMSParams.java
new file mode 100644
index 0000000..f5108f0
--- /dev/null
+++ b/src/java/com/android/internal/telephony/cat/SendSMSParams.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 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.cat;
+
+class SendSMSParams extends CommandParams {
+
+    TextMessage mTextSmsMsg;
+    TextMessage mDestAddress;
+    DisplayTextParams mDisplayText;
+
+    SendSMSParams(CommandDetails cmdDet, TextMessage textMsg, TextMessage destAddress,
+            DisplayTextParams displayText) {
+        super(cmdDet);
+        mTextSmsMsg = textMsg;
+        mDestAddress = destAddress;
+        mDisplayText = displayText;
+    }
+
+}
diff --git a/src/java/com/android/internal/telephony/cat/ValueParser.java b/src/java/com/android/internal/telephony/cat/ValueParser.java
index 7c09136..bd17f48 100644
--- a/src/java/com/android/internal/telephony/cat/ValueParser.java
+++ b/src/java/com/android/internal/telephony/cat/ValueParser.java
@@ -18,16 +18,24 @@
 
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
+import android.telephony.SmsMessage;
 
 import com.android.internal.telephony.GsmAlphabet;
 import com.android.internal.telephony.cat.Duration.TimeUnit;
 import com.android.internal.telephony.uicc.IccUtils;
 
+import java.io.ByteArrayOutputStream;
 import java.io.UnsupportedEncodingException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
-abstract class ValueParser {
+
+/**
+ *  Util class that parses different entities from the ctlvs ComprehensionTlv List
+ * @hide
+ */
+public abstract class ValueParser {
 
     /**
      * Search for a Command Details object from a list.
@@ -352,4 +360,42 @@
             throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
         }
     }
+
+    /**
+     * Retrieve's the tpdu from the ctlv and creates the SmsMessage from pdu.
+     * @param ctlv  ComprehensionTlv value
+     * @return message SmsMessage to retrieve the destAddress and Text
+     * @throws ResultException
+     * @hide
+     */
+    public static SmsMessage retrieveTpduAsSmsMessage(ComprehensionTlv ctlv)
+            throws ResultException {
+        if (ctlv != null) {
+            byte[] rawValue = ctlv.getRawValue();
+            int valueIndex = ctlv.getValueIndex();
+            int length = ctlv.getLength();
+            if (length != 0) {
+                try {
+                    byte[] pdu = Arrays.copyOfRange(rawValue, valueIndex, (valueIndex + length));
+                    ByteArrayOutputStream bo = new ByteArrayOutputStream(pdu.length + 1);
+                    /* Framework's TPdu Parser expects the TPdu be prepended with SC-Address.
+                     * else the parser will throw an exception. So prepending TPdu with 0,
+                     * which indicates that there is no SC address and its length is 0.
+                     * This way Parser will skip parsing for SC-Address
+                     */
+                    bo.write(0x00);
+                    bo.write(pdu, 0, pdu.length);
+                    byte[] frameworkPdu = bo.toByteArray();
+                    //ToDO handle for 3GPP2 format bug: b/243123533
+                    SmsMessage message = SmsMessage.createFromPdu(frameworkPdu,
+                            SmsMessage.FORMAT_3GPP);
+                    return message;
+                } catch (IndexOutOfBoundsException e) {
+                    throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+                }
+            }
+        }
+        return null;
+    }
+
 }
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java b/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
index eb5f866..784c974 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
@@ -21,6 +21,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.res.Resources;
+import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteCallback;
 import android.os.SystemProperties;
@@ -77,8 +78,8 @@
      * Create a new inbound SMS handler for CDMA.
      */
     private CdmaInboundSmsHandler(Context context, SmsStorageMonitor storageMonitor,
-            Phone phone, CdmaSMSDispatcher smsDispatcher) {
-        super("CdmaInboundSmsHandler", context, storageMonitor, phone);
+            Phone phone, CdmaSMSDispatcher smsDispatcher, Looper looper) {
+        super("CdmaInboundSmsHandler", context, storageMonitor, phone, looper);
         mSmsDispatcher = smsDispatcher;
         phone.mCi.setOnNewCdmaSms(getHandler(), EVENT_NEW_SMS, null);
 
@@ -169,9 +170,10 @@
      * Wait for state machine to enter startup state. We can't send any messages until then.
      */
     public static CdmaInboundSmsHandler makeInboundSmsHandler(Context context,
-            SmsStorageMonitor storageMonitor, Phone phone, CdmaSMSDispatcher smsDispatcher) {
+            SmsStorageMonitor storageMonitor, Phone phone, CdmaSMSDispatcher smsDispatcher,
+            Looper looper) {
         CdmaInboundSmsHandler handler = new CdmaInboundSmsHandler(context, storageMonitor,
-                phone, smsDispatcher);
+                phone, smsDispatcher, looper);
         handler.start();
         return handler;
     }
@@ -194,7 +196,8 @@
      * @return true if the message was handled here; false to continue processing
      */
     @Override
-    protected int dispatchMessageRadioSpecific(SmsMessageBase smsb, @SmsSource int smsSource) {
+    protected int dispatchMessageRadioSpecific(SmsMessageBase smsb, @SmsSource int smsSource,
+            int token) {
         SmsMessage sms = (SmsMessage) smsb;
         boolean isBroadcastType = (SmsEnvelope.MESSAGE_TYPE_BROADCAST == sms.getMessageType());
 
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaSmsBroadcastConfigInfo.java b/src/java/com/android/internal/telephony/cdma/CdmaSmsBroadcastConfigInfo.java
old mode 100755
new mode 100644
index b31df59..24ee56d
--- a/src/java/com/android/internal/telephony/cdma/CdmaSmsBroadcastConfigInfo.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaSmsBroadcastConfigInfo.java
@@ -17,6 +17,8 @@
 
 package com.android.internal.telephony.cdma;
 
+import java.util.Objects;
+
 /**
  * CdmaSmsBroadcastConfigInfo defines one configuration of Cdma Broadcast
  * Message to be received by the ME
@@ -84,4 +86,22 @@
             mFromServiceCategory + ", " + mToServiceCategory + "] " +
             (isSelected() ? "ENABLED" : "DISABLED");
     }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mFromServiceCategory, mToServiceCategory, mLanguage, mSelected);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof CdmaSmsBroadcastConfigInfo)) {
+            return false;
+        }
+
+        CdmaSmsBroadcastConfigInfo other = (CdmaSmsBroadcastConfigInfo) obj;
+
+        return mFromServiceCategory == other.mFromServiceCategory
+                && mToServiceCategory == other.mToServiceCategory
+                && mLanguage == other.mLanguage && mSelected == other.mSelected;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/data/AccessNetworksManager.java b/src/java/com/android/internal/telephony/data/AccessNetworksManager.java
index 6974837..267f70b 100644
--- a/src/java/com/android/internal/telephony/data/AccessNetworksManager.java
+++ b/src/java/com/android/internal/telephony/data/AccessNetworksManager.java
@@ -19,19 +19,19 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.StringDef;
 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.IBinder;
 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.SystemProperties;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
@@ -52,14 +52,11 @@
 import android.util.SparseArray;
 
 import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.RIL;
 import com.android.internal.telephony.SlidingWindowEventCounter;
 import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -77,35 +74,9 @@
  */
 public class AccessNetworksManager extends Handler {
     private static final boolean DBG = false;
-    public static final String SYSTEM_PROPERTIES_IWLAN_OPERATION_MODE =
-            "ro.telephony.iwlan_operation_mode";
 
-    @Retention(RetentionPolicy.SOURCE)
-    @StringDef(prefix = {"IWLAN_OPERATION_MODE_"},
-            value = {
-                    IWLAN_OPERATION_MODE_DEFAULT,
-                    IWLAN_OPERATION_MODE_LEGACY,
-                    IWLAN_OPERATION_MODE_AP_ASSISTED})
-    public @interface IwlanOperationMode {}
-
-    /**
-     * IWLAN default mode. On device that has IRadio 1.4 or above, it means
-     * {@link #IWLAN_OPERATION_MODE_AP_ASSISTED}. On device that has IRadio 1.3 or below, it means
-     * {@link #IWLAN_OPERATION_MODE_LEGACY}.
-     */
-    public static final String IWLAN_OPERATION_MODE_DEFAULT = "default";
-
-    /**
-     * IWLAN legacy mode. IWLAN is completely handled by the modem, and when the device is on
-     * IWLAN, modem reports IWLAN as a RAT.
-     */
-    public static final String IWLAN_OPERATION_MODE_LEGACY = "legacy";
-
-    /**
-     * IWLAN application processor assisted mode. IWLAN is handled by the bound IWLAN data service
-     * and network service separately.
-     */
-    public static final String IWLAN_OPERATION_MODE_AP_ASSISTED = "AP-assisted";
+    /** Event to guide a transport type for initial data connection of emergency data network. */
+    private static final int EVENT_GUIDE_TRANSPORT_TYPE_FOR_EMERGENCY = 1;
 
     /**
      * The counters to detect frequent QNS attempt to change preferred network transport by ApnType.
@@ -190,6 +161,19 @@
         }
     }
 
+    @Override
+    public void handleMessage(@NonNull Message msg) {
+        switch (msg.what) {
+            case EVENT_GUIDE_TRANSPORT_TYPE_FOR_EMERGENCY:
+                AsyncResult ar = (AsyncResult) msg.obj;
+                int transport = (int) ar.result;
+                onEmergencyDataNetworkPreferredTransportChanged(transport);
+                break;
+            default:
+                loge("Unexpected event " + msg.what);
+        }
+    }
+
     private class AccessNetworksManagerDeathRecipient implements IBinder.DeathRecipient {
         @Override
         public void binderDied() {
@@ -312,6 +296,20 @@
         }
     }
 
+    private void onEmergencyDataNetworkPreferredTransportChanged(
+            @AccessNetworkConstants.TransportType int transportType) {
+        try {
+            logl("onEmergencyDataNetworkPreferredTransportChanged: "
+                    + AccessNetworkConstants.transportTypeToString(transportType));
+            if (mIQualifiedNetworksService != null) {
+                mIQualifiedNetworksService.reportEmergencyDataNetworkPreferredTransportChanged(
+                        mPhone.getPhoneId(), transportType);
+            }
+        } catch (Exception ex) {
+            loge("onEmergencyDataNetworkPreferredTransportChanged: ", ex);
+        }
+    }
+
     /**
      * Access networks manager callback. This should be only used by {@link DataNetworkController}.
      */
@@ -346,36 +344,21 @@
                 Context.CARRIER_CONFIG_SERVICE);
         mLogTag = "ANM-" + mPhone.getPhoneId();
         mApnTypeToQnsChangeNetworkCounter = new SparseArray<>();
+        mAvailableTransports = new int[]{AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN};
 
-        if (isInLegacyMode()) {
-            log("operates in legacy mode.");
-            // For legacy mode, WWAN is the only transport to handle all data connections, even
-            // the IWLAN ones.
-            mAvailableTransports = new int[]{AccessNetworkConstants.TRANSPORT_TYPE_WWAN};
-        } else {
-            log("operates in AP-assisted mode.");
-            mAvailableTransports = new int[]{AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
-                    AccessNetworkConstants.TRANSPORT_TYPE_WLAN};
-
-            // bindQualifiedNetworksService posts real work to handler thread. So here we can
-            // let the callback execute in binder thread to avoid post twice.
-            mCarrierConfigManager.registerCarrierConfigChangeListener(Runnable::run,
-                    (slotIndex, subId, carrierId, specificCarrierId) -> {
-                        if (slotIndex != mPhone.getPhoneId()) return;
-                        // We should wait for carrier config changed event because the target
-                        // binding
-                        // package name can come from the carrier config. Note that we still get
-                        // this
-                        // event even when SIM is absent.
-                        if (DBG) {
-                            log(
-                                    "Carrier config changed. Try to bind qualified network "
-                                            + "service.");
-                        }
-                        bindQualifiedNetworksService();
-                    });
-            bindQualifiedNetworksService();
-        }
+        // bindQualifiedNetworksService posts real work to handler thread. So here we can
+        // let the callback execute in binder thread to avoid post twice.
+        mCarrierConfigManager.registerCarrierConfigChangeListener(Runnable::run,
+                (slotIndex, subId, carrierId, specificCarrierId) -> {
+                    if (slotIndex != mPhone.getPhoneId()) return;
+                    // We should wait for carrier config changed event because the target binding
+                    // package name can come from the carrier config. Note that we still get this
+                    // event even when SIM is absent.
+                    if (DBG) log("Carrier config changed. Try to bind qualified network service.");
+                    bindQualifiedNetworksService();
+                });
+        bindQualifiedNetworksService();
 
         // Using post to delay the registering because data retry manager and data config
         // manager instances are created later than access networks manager.
@@ -403,6 +386,8 @@
                             mApnTypeToQnsChangeNetworkCounter.clear();
                         }
                     });
+            mPhone.registerForEmergencyDomainSelected(
+                    this, EVENT_GUIDE_TRANSPORT_TYPE_FOR_EMERGENCY, null);
         });
     }
 
@@ -478,8 +463,7 @@
     /**
      * Get the qualified network service package.
      *
-     * @return package name of the qualified networks service package. Return empty string when in
-     * legacy mode (i.e. Dedicated IWLAN data/network service is not supported).
+     * @return package name of the qualified networks service package.
      */
     private String getQualifiedNetworksServicePackageName() {
         // Read package name from the resource
@@ -568,30 +552,9 @@
     }
 
     /**
-     * @return {@code true} if the device operates in legacy mode, otherwise {@code false}.
+     * @return The available transports.
      */
-    public boolean isInLegacyMode() {
-        // Get IWLAN operation mode from the system property. If the system property is configured
-        // to default or not configured, the mode is tied to IRadio version. For 1.4 or above, it's
-        // AP-assisted mode, for 1.3 or below, it's legacy mode.
-        String mode = SystemProperties.get(SYSTEM_PROPERTIES_IWLAN_OPERATION_MODE);
-
-        if (mode.equals(IWLAN_OPERATION_MODE_AP_ASSISTED)) {
-            return false;
-        } else if (mode.equals(IWLAN_OPERATION_MODE_LEGACY)) {
-            return true;
-        }
-
-        return mPhone.getHalVersion().less(RIL.RADIO_HAL_VERSION_1_4);
-    }
-
-    /**
-     * @return The available transports. Note that on legacy devices, the only available transport
-     * would be WWAN only. If the device is configured as AP-assisted mode, the available transport
-     * will always be WWAN and WLAN (even if the device is not camped on IWLAN).
-     * See {@link #isInLegacyMode()} for mode details.
-     */
-    public synchronized @NonNull int[] getAvailableTransports() {
+    public @NonNull int[] getAvailableTransports() {
         return mAvailableTransports;
     }
 
@@ -626,11 +589,6 @@
      * @return The preferred transport.
      */
     public @TransportType int getPreferredTransport(@ApnType int apnType) {
-        // In legacy mode, always preferred on cellular.
-        if (isInLegacyMode()) {
-            return AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
-        }
-
         return mPreferredTransports.get(apnType) == null
                 ? AccessNetworkConstants.TRANSPORT_TYPE_WWAN : mPreferredTransports.get(apnType);
     }
@@ -733,9 +691,6 @@
         }
 
         pw.decreaseIndent();
-        pw.println("isInLegacy=" + isInLegacyMode());
-        pw.println("IWLAN operation mode="
-                + SystemProperties.get(SYSTEM_PROPERTIES_IWLAN_OPERATION_MODE));
         pw.println("Local logs=");
         pw.increaseIndent();
         mLocalLog.dump(fd, pw, args);
diff --git a/src/java/com/android/internal/telephony/data/CellularNetworkValidator.java b/src/java/com/android/internal/telephony/data/CellularNetworkValidator.java
index aa830ae..c1d1203 100644
--- a/src/java/com/android/internal/telephony/data/CellularNetworkValidator.java
+++ b/src/java/com/android/internal/telephony/data/CellularNetworkValidator.java
@@ -40,7 +40,6 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConfigurationManager;
 import com.android.internal.telephony.PhoneFactory;
-import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
@@ -166,17 +165,9 @@
 
         private String getValidationNetworkIdentity(int subId) {
             if (!SubscriptionManager.isUsableSubscriptionId(subId)) return null;
-            Phone phone;
-            if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-                if (SubscriptionManagerService.getInstance() == null) return null;
-                phone = PhoneFactory.getPhone(SubscriptionManagerService.getInstance()
-                        .getPhoneId(subId));
-            } else {
-                SubscriptionController subController = SubscriptionController.getInstance();
-                if (subController == null) return null;
-                phone = PhoneFactory.getPhone(subController.getPhoneId(subId));
-            }
-
+            if (SubscriptionManagerService.getInstance() == null) return null;
+            Phone phone = PhoneFactory.getPhone(SubscriptionManagerService.getInstance()
+                    .getPhoneId(subId));
             if (phone == null || phone.getServiceState() == null) return null;
 
             NetworkRegistrationInfo regInfo = phone.getServiceState().getNetworkRegistrationInfo(
@@ -267,20 +258,12 @@
         // If it's already validating the same subscription, do nothing.
         if (subId == mSubId) return;
 
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
-                    .getSubscriptionInfoInternal(subId);
-            if (subInfo == null || !subInfo.isActive()) {
-                logd("Failed to start validation. Inactive subId " + subId);
-                callback.onValidationDone(false, subId);
-                return;
-            }
-        } else {
-            if (!SubscriptionController.getInstance().isActiveSubId(subId)) {
-                logd("Failed to start validation. Inactive subId " + subId);
-                callback.onValidationDone(false, subId);
-                return;
-            }
+        SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
+                .getSubscriptionInfoInternal(subId);
+        if (subInfo == null || !subInfo.isActive()) {
+            logd("Failed to start validation. Inactive subId " + subId);
+            callback.onValidationDone(false, subId);
+            return;
         }
 
         if (isValidating()) {
diff --git a/src/java/com/android/internal/telephony/data/DataConfigManager.java b/src/java/com/android/internal/telephony/data/DataConfigManager.java
index 731aac7..78450a8 100644
--- a/src/java/com/android/internal/telephony/data/DataConfigManager.java
+++ b/src/java/com/android/internal/telephony/data/DataConfigManager.java
@@ -72,12 +72,6 @@
     /** The default timeout in ms for data network stuck in a transit state. */
     private static final int DEFAULT_NETWORK_TRANSIT_STATE_TIMEOUT_MS = 300000;
 
-    /** Default time threshold in ms to define a internet connection status to be stable. */
-    public static int DEFAULT_AUTO_DATA_SWITCH_STABILITY_TIME_MS = 10000;
-
-    /** The max number of retries when a pre-switching validation fails. */
-    public static int DEFAULT_AUTO_DATA_SWITCH_MAX_RETRY = 7;
-
     /** Event for carrier config changed. */
     private static final int EVENT_CARRIER_CONFIG_CHANGED = 1;
 
@@ -268,18 +262,6 @@
      */
     private boolean mIsApnConfigAnomalyReportEnabled;
 
-    /**
-     * 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.
-     */
-    private long mAutoDataSwitchAvailabilityStabilityTimeThreshold;
-
-    /**
-     * The maximum number of retries when a pre-switching validation fails.
-     */
-    private int mAutoDataSwitchValidationMaxRetry;
-
     private @NonNull final Phone mPhone;
     private @NonNull final String mLogTag;
 
@@ -438,12 +420,6 @@
                 KEY_ANOMALY_NETWORK_HANDOVER_TIMEOUT, DEFAULT_NETWORK_TRANSIT_STATE_TIMEOUT_MS);
         mIsApnConfigAnomalyReportEnabled = properties.getBoolean(
                 KEY_ANOMALY_APN_CONFIG_ENABLED, false);
-        mAutoDataSwitchAvailabilityStabilityTimeThreshold = properties.getInt(
-                KEY_AUTO_DATA_SWITCH_AVAILABILITY_STABILITY_TIME_THRESHOLD,
-                DEFAULT_AUTO_DATA_SWITCH_STABILITY_TIME_MS);
-        mAutoDataSwitchValidationMaxRetry = properties.getInt(
-                KEY_AUTO_DATA_SWITCH_VALIDATION_MAX_RETRY,
-                DEFAULT_AUTO_DATA_SWITCH_MAX_RETRY);
     }
 
     /**
@@ -712,6 +688,12 @@
         return mShouldKeepNetworkUpInNonVops;
     }
 
+    /** {@code True} requires ping test to pass on the target slot before switching to it.*/
+    public boolean isPingTestBeforeAutoDataSwitchRequired() {
+        return mResources.getBoolean(com.android.internal.R.bool
+                .auto_data_switch_ping_test_before_switch);
+    }
+
     /**
      * @return Whether {@link NetworkCapabilities#NET_CAPABILITY_TEMPORARILY_NOT_METERED}
      * is supported by the carrier.
@@ -939,7 +921,8 @@
      * @return The maximum number of retries when a validation for switching failed.
      */
     public int getAutoDataSwitchValidationMaxRetry() {
-        return mAutoDataSwitchValidationMaxRetry;
+        return mResources.getInteger(com.android.internal.R.integer
+                .auto_data_switch_validation_max_retry);
     }
 
     /**
@@ -948,7 +931,8 @@
      * auto switch feature disabled.
      */
     public long getAutoDataSwitchAvailabilityStabilityTimeThreshold() {
-        return mAutoDataSwitchAvailabilityStabilityTimeThreshold;
+        return mResources.getInteger(com.android.internal.R.integer
+                .auto_data_switch_availability_stability_time_threshold_millis);
     }
 
     /**
@@ -1006,7 +990,7 @@
      * @return {@code true} if tearing down IMS data network should be delayed until the voice call
      * ends.
      */
-    public boolean isImsDelayTearDownEnabled() {
+    public boolean isImsDelayTearDownUntilVoiceCallEndEnabled() {
         return mCarrierConfig.getBoolean(
                 CarrierConfigManager.KEY_DELAY_IMS_TEAR_DOWN_UNTIL_CALL_END_BOOL);
     }
@@ -1331,9 +1315,9 @@
         pw.println("mNetworkDisconnectingTimeout=" + mNetworkDisconnectingTimeout);
         pw.println("mNetworkHandoverTimeout=" + mNetworkHandoverTimeout);
         pw.println("mIsApnConfigAnomalyReportEnabled=" + mIsApnConfigAnomalyReportEnabled);
-        pw.println("mAutoDataSwitchAvailabilityStabilityTimeThreshold="
-                + mAutoDataSwitchAvailabilityStabilityTimeThreshold);
-        pw.println("mAutoDataSwitchValidationMaxRetry=" + mAutoDataSwitchValidationMaxRetry);
+        pw.println("getAutoDataSwitchAvailabilityStabilityTimeThreshold="
+                + getAutoDataSwitchAvailabilityStabilityTimeThreshold());
+        pw.println("getAutoDataSwitchValidationMaxRetry=" + getAutoDataSwitchValidationMaxRetry());
         pw.println("Metered APN types=" + mMeteredApnTypes.stream()
                 .map(ApnSetting::getApnTypeString).collect(Collectors.joining(",")));
         pw.println("Roaming metered APN types=" + mRoamingMeteredApnTypes.stream()
@@ -1344,6 +1328,8 @@
                 .stream().map(DataUtils::networkCapabilityToString)
                 .collect(Collectors.joining(",")));
         pw.println("mShouldKeepNetworkUpInNoVops=" + mShouldKeepNetworkUpInNonVops);
+        pw.println("isPingTestBeforeAutoDataSwitchRequired="
+                + isPingTestBeforeAutoDataSwitchRequired());
         pw.println("Unmetered network types=" + String.join(",", mUnmeteredNetworkTypes));
         pw.println("Roaming unmetered network types="
                 + String.join(",", mRoamingUnmeteredNetworkTypes));
@@ -1368,7 +1354,8 @@
                 + shouldPersistIwlanDataNetworksWhenDataServiceRestarted());
         pw.println("Bandwidth estimation source=" + mResources.getString(
                 com.android.internal.R.string.config_bandwidthEstimateSource));
-        pw.println("isDelayTearDownImsEnabled=" + isImsDelayTearDownEnabled());
+        pw.println("isImsDelayTearDownUntilVoiceCallEndEnabled="
+                + isImsDelayTearDownUntilVoiceCallEndEnabled());
         pw.println("isEnhancedIwlanHandoverCheckEnabled=" + isEnhancedIwlanHandoverCheckEnabled());
         pw.println("isTetheringProfileDisabledForRoaming="
                 + isTetheringProfileDisabledForRoaming());
diff --git a/src/java/com/android/internal/telephony/data/DataEvaluation.java b/src/java/com/android/internal/telephony/data/DataEvaluation.java
index f13e66b..2ba3fe4 100644
--- a/src/java/com/android/internal/telephony/data/DataEvaluation.java
+++ b/src/java/com/android/internal/telephony/data/DataEvaluation.java
@@ -323,7 +323,9 @@
         /** Only one data network is allowed at one time. */
         ONLY_ALLOWED_SINGLE_NETWORK(true),
         /** Data enabled settings are not ready. */
-        DATA_SETTINGS_NOT_READY(true);
+        DATA_SETTINGS_NOT_READY(true),
+        /** Handover max retry stopped but network is not on the preferred transport. */
+        HANDOVER_RETRY_STOPPED(true);
 
         private final boolean mIsHardReason;
 
@@ -362,6 +364,10 @@
          */
         NORMAL,
         /**
+         * Data is allowed because an ongoing VoPS call depends on this network
+         */
+        IN_VOICE_CALL,
+        /**
          * The network brought up by this network request is unmetered. Should allowed no matter
          * the user enables or disables data.
          */
diff --git a/src/java/com/android/internal/telephony/data/DataNetwork.java b/src/java/com/android/internal/telephony/data/DataNetwork.java
index 4e165f7..d533933 100644
--- a/src/java/com/android/internal/telephony/data/DataNetwork.java
+++ b/src/java/com/android/internal/telephony/data/DataNetwork.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony.data;
 
+import static android.telephony.TelephonyManager.HAL_SERVICE_DATA;
+
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -52,6 +54,7 @@
 import android.telephony.Annotation.NetworkType;
 import android.telephony.Annotation.ValidationStatus;
 import android.telephony.AnomalyReporter;
+import android.telephony.CarrierConfigManager;
 import android.telephony.DataFailCause;
 import android.telephony.DataSpecificRegistrationInfo;
 import android.telephony.LinkCapacityEstimate;
@@ -923,8 +926,9 @@
         mDataAllowedReason = dataAllowedReason;
         dataProfile.setLastSetupTimestamp(SystemClock.elapsedRealtime());
         mAttachedNetworkRequestList.addAll(networkRequestList);
-        mCid.put(AccessNetworkConstants.TRANSPORT_TYPE_WWAN, INVALID_CID);
-        mCid.put(AccessNetworkConstants.TRANSPORT_TYPE_WLAN, INVALID_CID);
+        for (int transportType : mAccessNetworksManager.getAvailableTransports()) {
+            mCid.put(transportType, INVALID_CID);
+        }
         mTelephonyDisplayInfo = mPhone.getDisplayInfoController().getTelephonyDisplayInfo();
         mTcpBufferSizes = mDataConfigManager.getTcpConfigString(mTelephonyDisplayInfo);
 
@@ -1336,13 +1340,14 @@
                 }
             }
 
-            // If we've ever received PCO data before connected, now it's the time to
-            // process it.
+            // If we've ever received PCO data before connected, now it's the time to process it.
             mPcoData.getOrDefault(mCid.get(mTransport), Collections.emptyMap())
                     .forEach((pcoId, pcoData) -> {
                         onPcoDataChanged(pcoData);
                     });
 
+            mDataNetworkCallback.invokeFromExecutor(
+                    () -> mDataNetworkCallback.onLinkStatusChanged(DataNetwork.this, mLinkStatus));
             notifyPreciseDataConnectionState();
             updateSuspendState();
         }
@@ -1594,6 +1599,9 @@
             //************************************************************//
 
             if (mEverConnected) {
+                mLinkStatus = DataCallResponse.LINK_STATUS_INACTIVE;
+                mDataNetworkCallback.invokeFromExecutor(() -> mDataNetworkCallback
+                        .onLinkStatusChanged(DataNetwork.this, mLinkStatus));
                 mDataNetworkCallback.invokeFromExecutor(() -> mDataNetworkCallback
                         .onDisconnected(DataNetwork.this, mFailCause, mTearDownReason));
                 if (mTransport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
@@ -2298,8 +2306,13 @@
         if (mLinkStatus != response.getLinkStatus()) {
             mLinkStatus = response.getLinkStatus();
             log("Link status updated to " + DataUtils.linkStatusToString(mLinkStatus));
-            mDataNetworkCallback.invokeFromExecutor(
-                    () -> mDataNetworkCallback.onLinkStatusChanged(DataNetwork.this, mLinkStatus));
+            if (isConnected()) {
+                // If the data network is in a transition state, the link status will be notified
+                // upon entering connected or disconnected state. If the data network is already
+                // connected, send the updated link status from the updated data call response.
+                mDataNetworkCallback.invokeFromExecutor(() -> mDataNetworkCallback
+                        .onLinkStatusChanged(DataNetwork.this, mLinkStatus));
+            }
         }
 
         // Set link addresses
@@ -2572,9 +2585,9 @@
             log("Remove network since deactivate request returned an error.");
             mFailCause = DataFailCause.RADIO_NOT_AVAILABLE;
             transitionTo(mDisconnectedState);
-        } else if (mPhone.getHalVersion().less(RIL.RADIO_HAL_VERSION_2_0)) {
+        } else if (mPhone.getHalVersion(HAL_SERVICE_DATA).less(RIL.RADIO_HAL_VERSION_2_0)) {
             log("Remove network on deactivate data response on old HAL "
-                    + mPhone.getHalVersion());
+                    + mPhone.getHalVersion(HAL_SERVICE_DATA));
             mFailCause = DataFailCause.LOST_CONNECTION;
             transitionTo(mDisconnectedState);
         }
@@ -2609,16 +2622,17 @@
                 reason == TEAR_DOWN_REASON_AIRPLANE_MODE_ON ? DataService.REQUEST_REASON_SHUTDOWN
                         : DataService.REQUEST_REASON_NORMAL,
                 obtainMessage(EVENT_DEACTIVATE_DATA_NETWORK_RESPONSE));
-        mDataCallSessionStats.setDeactivateDataCallReason(DataService.REQUEST_REASON_NORMAL);
+        mDataCallSessionStats.setDeactivateDataCallReason(reason);
         mInvokedDataDeactivation = true;
     }
 
     /**
-     * @return {@code true} if this is an IMS network and tear down should be delayed until call
-     * ends on this data network.
+     * @return {@code true} if we shall delay tear down this network because an active voice call is
+     * relying on it and
+     * {@link CarrierConfigManager#KEY_DELAY_IMS_TEAR_DOWN_UNTIL_CALL_END_BOOL} is enabled.
      */
     public boolean shouldDelayImsTearDownDueToInCall() {
-        return mDataConfigManager.isImsDelayTearDownEnabled()
+        return mDataConfigManager.isImsDelayTearDownUntilVoiceCallEndEnabled()
                 && mNetworkCapabilities != null
                 && mNetworkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL)
                 && mPhone.getImsPhone() != null
diff --git a/src/java/com/android/internal/telephony/data/DataNetworkController.java b/src/java/com/android/internal/telephony/data/DataNetworkController.java
index fb06e14..2ee1ffd 100644
--- a/src/java/com/android/internal/telephony/data/DataNetworkController.java
+++ b/src/java/com/android/internal/telephony/data/DataNetworkController.java
@@ -561,10 +561,10 @@
         /**
          * Called when internet data network is connected.
          *
-         * @param dataProfiles The data profiles of the connected internet data network. It should
-         * be only one in most of the cases.
+         * @param internetNetworks The connected internet data network. It should be only one in
+         *                         most of the cases.
          */
-        public void onInternetDataNetworkConnected(@NonNull List<DataProfile> dataProfiles) {}
+        public void onInternetDataNetworkConnected(@NonNull List<DataNetwork> internetNetworks) {}
 
         /**
          * Called when data network is connected.
@@ -801,13 +801,10 @@
         log("DataNetworkController created.");
 
         mAccessNetworksManager = phone.getAccessNetworksManager();
-        mDataServiceManagers.put(AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
-                new DataServiceManager(mPhone, looper, AccessNetworkConstants.TRANSPORT_TYPE_WWAN));
-        if (!mAccessNetworksManager.isInLegacyMode()) {
-            mDataServiceManagers.put(AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
-                    new DataServiceManager(mPhone, looper,
-                            AccessNetworkConstants.TRANSPORT_TYPE_WLAN));
+        for (int transport : mAccessNetworksManager.getAvailableTransports()) {
+            mDataServiceManagers.put(transport, new DataServiceManager(mPhone, looper, transport));
         }
+
         mDataConfigManager = new DataConfigManager(mPhone, looper);
 
         // ========== Anomaly counters ==========
@@ -913,24 +910,7 @@
                     public void onDataNetworkHandoverRetryStopped(
                             @NonNull DataNetwork dataNetwork) {
                         Objects.requireNonNull(dataNetwork);
-                        int preferredTransport = mAccessNetworksManager
-                                .getPreferredTransportByNetworkCapability(
-                                        dataNetwork.getApnTypeNetworkCapability());
-                        if (dataNetwork.getTransport() == preferredTransport) {
-                            log("onDataNetworkHandoverRetryStopped: " + dataNetwork + " is already "
-                                    + "on the preferred transport "
-                                    + AccessNetworkConstants.transportTypeToString(
-                                            preferredTransport));
-                            return;
-                        }
-                        if (dataNetwork.shouldDelayImsTearDownDueToInCall()) {
-                            log("onDataNetworkHandoverRetryStopped: Delay IMS tear down until call "
-                                    + "ends. " + dataNetwork);
-                            return;
-                        }
-
-                        tearDownGracefully(dataNetwork,
-                                DataNetwork.TEAR_DOWN_REASON_HANDOVER_FAILED);
+                        DataNetworkController.this.onDataNetworkHandoverRetryStopped(dataNetwork);
                     }
                 });
         mImsManager = mPhone.getContext().getSystemService(ImsManager.class);
@@ -1011,12 +991,10 @@
         mDataServiceManagers.get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
                 .registerForServiceBindingChanged(this, EVENT_DATA_SERVICE_BINDING_CHANGED);
 
-        if (!mAccessNetworksManager.isInLegacyMode()) {
-            mPhone.getServiceStateTracker().registerForServiceStateChanged(this,
-                    EVENT_SERVICE_STATE_CHANGED, null);
-            mDataServiceManagers.get(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
-                    .registerForServiceBindingChanged(this, EVENT_DATA_SERVICE_BINDING_CHANGED);
-        }
+        mPhone.getServiceStateTracker().registerForServiceStateChanged(this,
+                EVENT_SERVICE_STATE_CHANGED, null);
+        mDataServiceManagers.get(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
+                .registerForServiceBindingChanged(this, EVENT_DATA_SERVICE_BINDING_CHANGED);
 
         mPhone.getContext().getSystemService(TelephonyRegistryManager.class)
                 .addOnSubscriptionsChangedListener(new OnSubscriptionsChangedListener() {
@@ -1640,13 +1618,11 @@
                         reason.isConditionBased());
         if (dataProfile == null) {
             evaluation.addDataDisallowedReason(DataDisallowedReason.NO_SUITABLE_DATA_PROFILE);
-        } else if (reason == DataEvaluationReason.NEW_REQUEST
-                && (mDataRetryManager.isAnySetupRetryScheduled(dataProfile, transport)
-                || mDataRetryManager.isSimilarNetworkRequestRetryScheduled(
-                        networkRequest, transport))) {
-            // If this is a new request, check if there is any retry already scheduled. For all
-            // other evaluation reasons, since they are all condition changes, so if there is any
-            // retry scheduled, we still want to go ahead and setup the data network.
+        } else if (// Check for new requests if we already self-scheduled(as opposed to modem
+            // demanded) retry for similar requests.
+                reason == DataEvaluationReason.NEW_REQUEST
+                        &&  mDataRetryManager.isSimilarNetworkRequestRetryScheduled(
+                        networkRequest, transport)) {
             evaluation.addDataDisallowedReason(DataDisallowedReason.RETRY_SCHEDULED);
         } else if (mDataRetryManager.isDataProfileThrottled(dataProfile, transport)) {
             evaluation.addDataDisallowedReason(DataDisallowedReason.DATA_THROTTLED);
@@ -1771,15 +1747,20 @@
             }
         }
 
-        boolean isMmtel = false;
-        // If the data network is IMS that supports voice call, and has MMTEL request (client
-        // specified VoPS is required.)
-        if (dataNetwork.getAttachedNetworkRequestList().get(
-                new int[]{NetworkCapabilities.NET_CAPABILITY_MMTEL}) != null) {
-            // When reaching here, it means the network supports MMTEL, and also has MMTEL request
-            // attached to it.
-            isMmtel = true;
-            if (!dataNetwork.shouldDelayImsTearDownDueToInCall()) {
+        boolean vopsIsRequired = dataNetwork.hasNetworkCapabilityInNetworkRequests(
+                NetworkCapabilities.NET_CAPABILITY_MMTEL);
+
+        // Check an active call relying on this network and config for "delay tear down due to vops
+        // call" is enabled.
+        if (dataNetwork.shouldDelayImsTearDownDueToInCall()) {
+            if (vopsIsRequired) {
+                log("Ignored VoPS check due to delay IMS tear down until call ends.");
+            }
+        } else {
+            // Reach here means we should ignore active calls even if there are any.
+
+            // Check if VoPS requirement is met.
+            if (vopsIsRequired) {
                 if (dataNetwork.getTransport() == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
                     NetworkRegistrationInfo nri = mServiceState.getNetworkRegistrationInfo(
                             NetworkRegistrationInfo.DOMAIN_PS,
@@ -1794,16 +1775,20 @@
                         }
                     }
                 }
-            } else {
-                log("Ignored VoPS check due to delay IMS tear down until call ends.");
+            }
+
+            // Check if handover retry stopped and preferred transport still not matched.
+            int preferredTransport = mAccessNetworksManager
+                    .getPreferredTransportByNetworkCapability(
+                            dataNetwork.getApnTypeNetworkCapability());
+            if (preferredTransport != dataNetwork.getTransport()
+                    && mDataRetryManager.isDataNetworkHandoverRetryStopped(dataNetwork)) {
+                evaluation.addDataDisallowedReason(DataDisallowedReason.HANDOVER_RETRY_STOPPED);
             }
         }
 
         // Check if data is disabled
-        boolean dataDisabled = false;
-        if (!mDataSettingsManager.isDataEnabled()) {
-            dataDisabled = true;
-        }
+        boolean dataDisabled = !mDataSettingsManager.isDataEnabled();
 
         // Check if data roaming is disabled
         if (mServiceState.getDataRoaming() && !mDataSettingsManager.isDataRoamingEnabled()) {
@@ -1818,17 +1803,14 @@
         DataProfile dataProfile = dataNetwork.getDataProfile();
         if (dataProfile.getApnSetting() != null) {
             // Check if data is disabled for the APN type
-            dataDisabled = !mDataSettingsManager.isDataEnabled(DataUtils
-                    .networkCapabilityToApnType(DataUtils
-                            .getHighestPriorityNetworkCapabilityFromDataProfile(
-                                    mDataConfigManager, dataProfile)));
+            dataDisabled = !mDataSettingsManager.isDataEnabled(
+                    DataUtils.networkCapabilityToApnType(
+                            dataNetwork.getApnTypeNetworkCapability()));
 
             // Sometimes network temporarily OOS and network type becomes UNKNOWN. We don't
             // tear down network in that case.
             if (networkType != TelephonyManager.NETWORK_TYPE_UNKNOWN
-                    && !dataProfile.getApnSetting().canSupportLingeringNetworkType(networkType)
-                    // delay IMS tear down if SRVCC in progress
-                    && !(isMmtel && mIsSrvccHandoverInProcess)) {
+                    && !dataProfile.getApnSetting().canSupportLingeringNetworkType(networkType)) {
                 log("networkType=" + TelephonyManager.getNetworkTypeName(networkType)
                         + ", networkTypeBitmask="
                         + TelephonyManager.convertNetworkTypeBitmaskToString(
@@ -1855,7 +1837,8 @@
         // If users switch preferred profile in APN editor, we need to tear down network.
         if (dataNetwork.isInternetSupported()
                 && !mDataProfileManager.isDataProfilePreferred(dataProfile)
-                && mDataProfileManager.isAnyPreferredDataProfileExisting()) {
+                && mDataProfileManager.canPreferredDataProfileSatisfy(
+                        dataNetwork.getAttachedNetworkRequestList())) {
             evaluation.addDataDisallowedReason(DataDisallowedReason.DATA_PROFILE_NOT_PREFERRED);
         }
 
@@ -1891,6 +1874,15 @@
             }
         }
 
+        // Check if we allow additional lingering for active VoPS call network if
+        // a. this network is SRVCC handover in progress
+        // or b. "delay tear down due to active VoPS call" is enabled
+        boolean isInSrvcc = vopsIsRequired && mIsSrvccHandoverInProcess;
+        if (evaluation.containsOnly(DataDisallowedReason.DATA_NETWORK_TYPE_NOT_ALLOWED)
+                && (dataNetwork.shouldDelayImsTearDownDueToInCall() || isInSrvcc)) {
+            evaluation.addDataAllowedReason(DataAllowedReason.IN_VOICE_CALL);
+        }
+
         log("Evaluated " + dataNetwork + ", " + evaluation);
         return evaluation;
     }
@@ -2095,6 +2087,8 @@
                     return DataNetwork.TEAR_DOWN_REASON_VOPS_NOT_SUPPORTED;
                 case ONLY_ALLOWED_SINGLE_NETWORK:
                     return DataNetwork.TEAR_DOWN_REASON_ONLY_ALLOWED_SINGLE_NETWORK;
+                case HANDOVER_RETRY_STOPPED:
+                    return DataNetwork.TEAR_DOWN_REASON_HANDOVER_FAILED;
             }
         }
         return DataNetwork.TEAR_DOWN_REASON_NONE;
@@ -2814,6 +2808,32 @@
     }
 
     /**
+     * Called when data network reached max handover retry count.
+     *
+     * @param dataNetwork The data network.
+     */
+    private void onDataNetworkHandoverRetryStopped(@NonNull DataNetwork dataNetwork) {
+        int preferredTransport = mAccessNetworksManager
+                .getPreferredTransportByNetworkCapability(
+                        dataNetwork.getApnTypeNetworkCapability());
+        if (dataNetwork.getTransport() == preferredTransport) {
+            log("onDataNetworkHandoverRetryStopped: " + dataNetwork + " is already "
+                    + "on the preferred transport "
+                    + AccessNetworkConstants.transportTypeToString(
+                    preferredTransport));
+            return;
+        }
+        if (dataNetwork.shouldDelayImsTearDownDueToInCall()) {
+            log("onDataNetworkHandoverRetryStopped: Delay IMS tear down until call "
+                    + "ends. " + dataNetwork);
+            return;
+        }
+
+        tearDownGracefully(dataNetwork,
+                DataNetwork.TEAR_DOWN_REASON_HANDOVER_FAILED);
+    }
+
+    /**
      * Called when data network validation status changed.
      *
      * @param status one of {@link NetworkAgent#VALIDATION_STATUS_VALID} or
@@ -3165,6 +3185,16 @@
             logl("Start handover " + dataNetwork + " to "
                     + AccessNetworkConstants.transportTypeToString(targetTransport));
             dataNetwork.startHandover(targetTransport, dataHandoverRetryEntry);
+        } else if (dataEvaluation.containsOnly(DataDisallowedReason.NOT_IN_SERVICE)
+                && dataNetwork.shouldDelayImsTearDownDueToInCall()) {
+            // We try to preserve voice call in the case of temporary preferred transport mismatch
+            if (dataHandoverRetryEntry != null) {
+                dataHandoverRetryEntry.setState(DataRetryEntry.RETRY_STATE_FAILED);
+            }
+            mDataRetryManager.evaluateDataHandoverRetry(dataNetwork,
+                    DataFailCause.HANDOVER_FAILED,
+                    DataCallResponse.RETRY_DURATION_UNDEFINED /* retry mills */);
+            logl("tryHandoverDataNetwork: Scheduled retry due to in voice call and target OOS");
         } else if (dataEvaluation.containsAny(DataDisallowedReason.NOT_ALLOWED_BY_POLICY,
                 DataDisallowedReason.NOT_IN_SERVICE,
                 DataDisallowedReason.VOPS_NOT_SUPPORTED)) {
@@ -3469,9 +3499,7 @@
                     && mInternetDataNetworkState == TelephonyManager.DATA_DISCONNECTED) {
                 mDataNetworkControllerCallbacks.forEach(callback -> callback.invokeFromExecutor(
                         () -> callback.onInternetDataNetworkConnected(
-                                allConnectedInternetDataNetworks.stream()
-                                        .map(DataNetwork::getDataProfile)
-                                        .collect(Collectors.toList()))));
+                                allConnectedInternetDataNetworks)));
             } else if (dataNetworkState == TelephonyManager.DATA_DISCONNECTED
                     && mInternetDataNetworkState == TelephonyManager.DATA_CONNECTED) {
                 mDataNetworkControllerCallbacks.forEach(callback -> callback.invokeFromExecutor(
diff --git a/src/java/com/android/internal/telephony/data/DataProfileManager.java b/src/java/com/android/internal/telephony/data/DataProfileManager.java
index bcb9a36..893ec41 100644
--- a/src/java/com/android/internal/telephony/data/DataProfileManager.java
+++ b/src/java/com/android/internal/telephony/data/DataProfileManager.java
@@ -171,8 +171,8 @@
                 new DataNetworkControllerCallback(this::post) {
                     @Override
                     public void onInternetDataNetworkConnected(
-                            @NonNull List<DataProfile> dataProfiles) {
-                        DataProfileManager.this.onInternetDataNetworkConnected(dataProfiles);
+                            @NonNull List<DataNetwork> internetNetworks) {
+                        DataProfileManager.this.onInternetDataNetworkConnected(internetNetworks);
                     }
 
                     @Override
@@ -413,24 +413,37 @@
     /**
      * Called when internet data is connected.
      *
-     * @param dataProfiles The connected internet data networks' profiles.
+     * @param internetNetworks The connected internet data networks.
      */
-    private void onInternetDataNetworkConnected(@NonNull List<DataProfile> dataProfiles) {
-        // Most of the cases there should be only one, but in case there are multiple, choose the
-        // one which has longest life cycle.
-        DataProfile dataProfile = dataProfiles.stream()
-                .max(Comparator.comparingLong(DataProfile::getLastSetupTimestamp).reversed())
-                .orElse(null);
+    private void onInternetDataNetworkConnected(@NonNull List<DataNetwork> internetNetworks) {
+        DataProfile defaultProfile = null;
+        if (internetNetworks.size() == 1) {
+            // Most of the cases there should be only one.
+            defaultProfile = internetNetworks.get(0).getDataProfile();
+        } else if (internetNetworks.size() > 1) {
+            // but in case there are multiple, find the default internet network, and choose the
+            // one which has longest life cycle.
+            logv("onInternetDataNetworkConnected: mPreferredDataProfile=" + mPreferredDataProfile
+                    + " internetNetworks=" + internetNetworks);
+            defaultProfile = internetNetworks.stream()
+                    .filter(network -> mPreferredDataProfile == null
+                            || canPreferredDataProfileSatisfy(
+                            network.getAttachedNetworkRequestList()))
+                    .map(DataNetwork::getDataProfile)
+                    .min(Comparator.comparingLong(DataProfile::getLastSetupTimestamp))
+                    .orElse(null);
+        }
 
         // Update a working internet data profile as a future candidate for preferred data profile
         // after APNs are reset to default
-        mLastInternetDataProfile = dataProfile;
+        mLastInternetDataProfile = defaultProfile;
 
-        // If there is no preferred data profile, then we should use one of the data profiles,
-        // which is good for internet, as the preferred data profile.
-        if (mPreferredDataProfile != null) return;
+        // If the live default internet network is not using the preferred data profile, since
+        // brought up a network means it passed sophisticated checks, update the preferred data
+        // profile so that this network won't be torn down in future network evaluations.
+        if (defaultProfile == null || defaultProfile.equals(mPreferredDataProfile)) return;
         // Save the preferred data profile into database.
-        setPreferredDataProfile(dataProfile);
+        setPreferredDataProfile(defaultProfile);
         updateDataProfiles(ONLY_UPDATE_IA_IF_CHANGED);
     }
 
@@ -484,7 +497,7 @@
      * the preferred data profile from database.
      */
     private void setPreferredDataProfile(@Nullable DataProfile dataProfile) {
-        log("setPreferredDataProfile: " + dataProfile);
+        logl("setPreferredDataProfile: " + dataProfile);
 
         String subId = Long.toString(mPhone.getSubId());
         Uri uri = Uri.withAppendedPath(Telephony.Carriers.PREFERRED_APN_URI, subId);
@@ -783,6 +796,18 @@
     }
 
     /**
+     * @param networkRequests The required network requests
+     * @return {@code true} if we currently have a preferred data profile that's capable of
+     * satisfying the required network requests; {@code false} if we have no preferred, or the
+     * preferred cannot satisfy the required requests.
+     */
+    public boolean canPreferredDataProfileSatisfy(
+            @NonNull DataNetworkController.NetworkRequestList networkRequests) {
+        return mPreferredDataProfile != null && networkRequests.stream()
+                .allMatch(request -> request.canBeSatisfiedBy(mPreferredDataProfile));
+    }
+
+    /**
      * Check if there is tethering data profile for certain network type.
      *
      * @param networkType The network type
@@ -803,18 +828,6 @@
         return getDataProfileForNetworkRequest(networkRequest, networkType, true) != null;
     }
 
-     /**
-     * Check if any preferred data profile exists.
-     *
-     * @return {@code true} if any preferred data profile exists
-     */
-    public boolean isAnyPreferredDataProfileExisting() {
-        for (DataProfile dataProfile : mAllDataProfiles) {
-            if (dataProfile.isPreferred()) return true;
-        }
-        return false;
-    }
-
     /**
      * Dedupe the similar data profiles.
      */
@@ -1130,6 +1143,7 @@
         pw.println("Preferred data profile from db=" + getPreferredDataProfileFromDb());
         pw.println("Preferred data profile from config=" + getPreferredDataProfileFromConfig());
         pw.println("Preferred data profile set id=" + mPreferredDataProfileSetId);
+        pw.println("Last internet data profile=" + mLastInternetDataProfile);
         pw.println("Initial attach data profile=" + mInitialAttachDataProfile);
         pw.println("isTetheringDataProfileExisting=" + isTetheringDataProfileExisting(
                 TelephonyManager.NETWORK_TYPE_LTE));
diff --git a/src/java/com/android/internal/telephony/data/DataRetryManager.java b/src/java/com/android/internal/telephony/data/DataRetryManager.java
index ab653fe..b6ad101 100644
--- a/src/java/com/android/internal/telephony/data/DataRetryManager.java
+++ b/src/java/com/android/internal/telephony/data/DataRetryManager.java
@@ -21,6 +21,12 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.net.NetworkCapabilities;
 import android.os.AsyncResult;
 import android.os.Handler;
@@ -72,6 +78,11 @@
 public class DataRetryManager extends Handler {
     private static final boolean VDBG = false;
 
+    /** Intent of Alarm Manager for long retry timer. */
+    private static final String ACTION_RETRY = "com.android.internal.telephony.data.ACTION_RETRY";
+    /** The extra key for the hashcode of the retry entry for Alarm Manager. */
+    private static final String ACTION_RETRY_EXTRA_HASHCODE = "extra_retry_hashcode";
+
     /** Event for data setup retry. */
     private static final int EVENT_DATA_SETUP_RETRY = 3;
 
@@ -98,6 +109,12 @@
 
     /** The maximum entries to preserve. */
     private static final int MAXIMUM_HISTORICAL_ENTRIES = 100;
+    /**
+     * The threshold of retry 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);
 
     @IntDef(prefix = {"RESET_REASON_"},
             value = {
@@ -143,6 +160,9 @@
     /** Local log. */
     private final @NonNull LocalLog mLocalLog = new LocalLog(128);
 
+    /** Alarm Manager used to schedule long set up or handover retries. */
+    private final @NonNull AlarmManager mAlarmManager;
+
     /**
      * The data retry callback. This is only used to notify {@link DataNetworkController} to retry
      * setup data network.
@@ -952,16 +972,17 @@
         mDataServiceManagers = dataServiceManagers;
         mDataConfigManager = dataNetworkController.getDataConfigManager();
         mDataProfileManager = dataNetworkController.getDataProfileManager();
+        mAlarmManager = mPhone.getContext().getSystemService(AlarmManager.class);
+
         mDataConfigManager.registerCallback(new DataConfigManagerCallback(this::post) {
             @Override
             public void onCarrierConfigChanged() {
                 DataRetryManager.this.onCarrierConfigUpdated();
             }
         });
-        mDataServiceManagers.get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                .registerForApnUnthrottled(this, EVENT_DATA_PROFILE_UNTHROTTLED);
-        if (!mPhone.getAccessNetworksManager().isInLegacyMode()) {
-            mDataServiceManagers.get(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
+
+        for (int transport : mPhone.getAccessNetworksManager().getAvailableTransports()) {
+            mDataServiceManagers.get(transport)
                     .registerForApnUnthrottled(this, EVENT_DATA_PROFILE_UNTHROTTLED);
         }
         mDataProfileManager.registerCallback(new DataProfileManagerCallback(this::post) {
@@ -997,6 +1018,19 @@
         mRil.registerForOn(this, EVENT_RADIO_ON, null);
         mRil.registerForModemReset(this, EVENT_MODEM_RESET, null);
 
+        // Register intent of alarm manager for long retry timer
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(ACTION_RETRY);
+        mPhone.getContext().registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (ACTION_RETRY.equals(intent.getAction())) {
+                    DataRetryManager.this.onAlarmIntentRetry(
+                            intent.getIntExtra(ACTION_RETRY_EXTRA_HASHCODE, -1 /*Bad hashcode*/));
+                }
+            }
+        }, intentFilter);
+
         if (mDataConfigManager.shouldResetDataThrottlingWhenTacChanges()) {
             mPhone.getServiceStateTracker().registerForAreaCodeChanged(this, EVENT_TAC_CHANGED,
                     null);
@@ -1190,13 +1224,14 @@
                         return;
                     }
 
-                    int failedCount = getRetryFailedCount(capability, retryRule);
+                    int failedCount = getRetryFailedCount(capability, retryRule, transport);
                     log("For capability " + DataUtils.networkCapabilityToString(capability)
                             + ", found matching rule " + retryRule + ", failed count="
                             + failedCount);
                     if (failedCount == retryRule.getMaxRetries()) {
-                        log("Data retry failed for " + failedCount + " times. Stopped "
-                                + "timer-based data retry for "
+                        log("Data retry failed for " + failedCount + " times on "
+                                + AccessNetworkConstants.transportTypeToString(transport)
+                                + ". Stopped timer-based data retry for "
                                 + DataUtils.networkCapabilityToString(capability)
                                 + ". Condition-based retry will still happen when condition "
                                 + "changes.");
@@ -1299,6 +1334,24 @@
         }
     }
 
+    /**
+     * @param dataNetwork The data network to check.
+     * @return {@code true} if the data network had failed the maximum number of attempts for
+     * handover according to any retry rules.
+     */
+    public boolean isDataNetworkHandoverRetryStopped(@NonNull DataNetwork dataNetwork) {
+        // Matching the rule in configured order.
+        for (DataHandoverRetryRule retryRule : mDataHandoverRetryRuleList) {
+            int failedCount = getRetryFailedCount(dataNetwork, retryRule);
+            if (failedCount == retryRule.getMaxRetries()) {
+                log("Data handover retry failed for " + failedCount + " times. Stopped "
+                        + "handover retry.");
+                return true;
+            }
+        }
+        return false;
+    }
+
     /** Cancel all retries and throttling entries. */
     private void onReset(@RetryResetReason int reason) {
         logl("Remove all retry and throttling entries, reason=" + resetReasonToString(reason));
@@ -1354,16 +1407,18 @@
      *
      * @param networkCapability The network capability to check.
      * @param dataRetryRule The data retry rule.
+     * @param transport The transport on which setup failure has occurred.
      * @return The failed count since last successful data setup.
      */
     private int getRetryFailedCount(@NetCapability int networkCapability,
-            @NonNull DataSetupRetryRule dataRetryRule) {
+            @NonNull DataSetupRetryRule dataRetryRule, @TransportType int transport) {
         int count = 0;
         for (int i = mDataRetryEntries.size() - 1; i >= 0; i--) {
             if (mDataRetryEntries.get(i) instanceof DataSetupRetryEntry) {
                 DataSetupRetryEntry entry = (DataSetupRetryEntry) mDataRetryEntries.get(i);
                 // count towards the last succeeded data setup.
-                if (entry.setupRetryType == DataSetupRetryEntry.RETRY_TYPE_NETWORK_REQUESTS) {
+                if (entry.setupRetryType == DataSetupRetryEntry.RETRY_TYPE_NETWORK_REQUESTS
+                        && entry.transport == transport) {
                     if (entry.networkRequestList.isEmpty()) {
                         String msg = "Invalid data retry entry detected";
                         logl(msg);
@@ -1395,20 +1450,50 @@
      * @param dataRetryEntry The data retry entry.
      */
     private void schedule(@NonNull DataRetryEntry dataRetryEntry) {
-        logl("Scheduled data retry: " + dataRetryEntry);
+        logl("Scheduled data retry " + dataRetryEntry
+                + " hashcode=" + dataRetryEntry.hashCode());
         mDataRetryEntries.add(dataRetryEntry);
         if (mDataRetryEntries.size() >= MAXIMUM_HISTORICAL_ENTRIES) {
             // Discard the oldest retry entry.
             mDataRetryEntries.remove(0);
         }
 
-        // Using delayed message instead of alarm manager to schedule data retry is intentional.
-        // When the device enters doze mode, the handler message might be extremely delayed than the
-        // original scheduled time. There is no need to wake up the device to perform data retry in
-        // that case.
-        sendMessageDelayed(obtainMessage(dataRetryEntry instanceof DataSetupRetryEntry
-                        ? EVENT_DATA_SETUP_RETRY : EVENT_DATA_HANDOVER_RETRY, dataRetryEntry),
-                dataRetryEntry.retryDelayMillis);
+        // When the device is in doze mode, the handler message might be extremely delayed because
+        // handler uses relative system time(not counting sleep) which is inaccurate even when we
+        // enter the maintenance window.
+        // Therefore, we use alarm manager when we need to schedule long timers.
+        if (dataRetryEntry.retryDelayMillis <= RETRY_LONG_DELAY_TIMER_THRESHOLD_MILLIS) {
+            sendMessageDelayed(obtainMessage(dataRetryEntry instanceof DataSetupRetryEntry
+                            ? EVENT_DATA_SETUP_RETRY : EVENT_DATA_HANDOVER_RETRY, dataRetryEntry),
+                    dataRetryEntry.retryDelayMillis);
+        } else {
+            Intent intent = new Intent(ACTION_RETRY);
+            intent.putExtra(ACTION_RETRY_EXTRA_HASHCODE, dataRetryEntry.hashCode());
+            // No need to wake up the device at the exact time, the retry can wait util next time
+            // the device wake up to save power.
+            mAlarmManager.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME,
+                    dataRetryEntry.retryElapsedTime,
+                    PendingIntent.getBroadcast(mPhone.getContext(),
+                            dataRetryEntry.hashCode() /*Unique identifier of this retry attempt*/,
+                            intent,
+                            PendingIntent.FLAG_IMMUTABLE));
+        }
+    }
+
+    /**
+     * Called when it's time to retry scheduled by Alarm Manager.
+     * @param retryHashcode The hashcode is the unique identifier of which retry entry to retry.
+     */
+    private void onAlarmIntentRetry(int retryHashcode) {
+        DataRetryEntry dataRetryEntry = mDataRetryEntries.stream()
+                .filter(entry -> entry.hashCode() == retryHashcode)
+                .findAny()
+                .orElse(null);
+        logl("onAlarmIntentRetry: found " + dataRetryEntry + " with hashcode " + retryHashcode);
+        if (dataRetryEntry != null) {
+            sendMessage(obtainMessage(dataRetryEntry instanceof DataSetupRetryEntry
+                    ? EVENT_DATA_SETUP_RETRY : EVENT_DATA_HANDOVER_RETRY, dataRetryEntry));
+        }
     }
 
     /**
@@ -1431,15 +1516,20 @@
             @TransportType int transport, @ElapsedRealtimeLong long expirationTime) {
         DataThrottlingEntry entry = new DataThrottlingEntry(dataProfile, networkRequestList,
                 dataNetwork, transport, retryType, expirationTime);
-        if (mDataThrottlingEntries.size() >= MAXIMUM_HISTORICAL_ENTRIES) {
-            mDataThrottlingEntries.remove(0);
-        }
-
-        // Remove previous entry that contains the same data profile.
+        // Remove previous entry that contains the same data profile. Therefore it should always
+        // contain at maximum all the distinct data profiles of the current subscription.
         mDataThrottlingEntries.removeIf(
                 throttlingEntry -> dataProfile.equals(throttlingEntry.dataProfile));
 
-
+        if (mDataThrottlingEntries.size() >= MAXIMUM_HISTORICAL_ENTRIES) {
+            // If we don't see the anomaly report after U release, we should remove this check for
+            // the commented reason above.
+            AnomalyReporter.reportAnomaly(
+                    UUID.fromString("24fd4d46-1d0f-4b13-b7d6-7bad70b8289b"),
+                    "DataRetryManager throttling more than 100 data profiles",
+                    mPhone.getCarrierId());
+            mDataThrottlingEntries.remove(0);
+        }
         logl("Add throttling entry " + entry);
         mDataThrottlingEntries.add(entry);
 
@@ -1474,11 +1564,11 @@
      * When this is set, {@code dataProfile} must be {@code null}.
      * @param transport The transport that this unthrottling request is on.
      * @param remove Whether to remove unthrottled entries from the list of entries.
-     * @param retry Whether schedule data setup retry after unthrottling.
+     * @param retry Whether schedule retry after unthrottling.
      */
     private void onDataProfileUnthrottled(@Nullable DataProfile dataProfile, @Nullable String apn,
             @TransportType int transport, boolean remove, boolean retry) {
-        log("onDataProfileUnthrottled: data profile=" + dataProfile + ", apn=" + apn
+        log("onDataProfileUnthrottled: dataProfile=" + dataProfile + ", apn=" + apn
                 + ", transport=" + AccessNetworkConstants.transportTypeToString(transport)
                 + ", remove=" + remove);
 
@@ -1490,11 +1580,9 @@
             // equal to the data profiles kept in data profile manager (due to some fields missing
             // in DataProfileInfo.aidl), so we need to get the equivalent data profile from data
             // profile manager.
-            log("onDataProfileUnthrottled: dataProfile=" + dataProfile);
             Stream<DataThrottlingEntry> stream = mDataThrottlingEntries.stream();
             stream = stream.filter(entry -> entry.expirationTimeMillis > now);
             if (dataProfile.getApnSetting() != null) {
-                dataProfile.getApnSetting().setPermanentFailed(false);
                 stream = stream
                         .filter(entry -> entry.dataProfile.getApnSetting() != null)
                         .filter(entry -> entry.dataProfile.getApnSetting().getApnName()
@@ -1535,6 +1623,7 @@
         final int dataRetryType = retryType;
 
         if (unthrottledProfile != null && unthrottledProfile.getApnSetting() != null) {
+            unthrottledProfile.getApnSetting().setPermanentFailed(false);
             throttleStatusList.addAll(unthrottledProfile.getApnSetting().getApnTypes().stream()
                     .map(apnType -> new ThrottleStatus.Builder()
                             .setApnType(apnType)
@@ -1618,12 +1707,14 @@
      */
     public boolean isSimilarNetworkRequestRetryScheduled(
             @NonNull TelephonyNetworkRequest networkRequest, @TransportType int transport) {
+        long now = SystemClock.elapsedRealtime();
         for (int i = mDataRetryEntries.size() - 1; i >= 0; i--) {
             if (mDataRetryEntries.get(i) instanceof DataSetupRetryEntry) {
                 DataSetupRetryEntry entry = (DataSetupRetryEntry) mDataRetryEntries.get(i);
                 if (entry.getState() == DataRetryEntry.RETRY_STATE_NOT_RETRIED
                         && entry.setupRetryType
-                        == DataSetupRetryEntry.RETRY_TYPE_NETWORK_REQUESTS) {
+                        == DataSetupRetryEntry.RETRY_TYPE_NETWORK_REQUESTS
+                        && entry.retryElapsedTime > now) {
                     if (entry.networkRequestList.isEmpty()) {
                         String msg = "Invalid data retry entry detected";
                         logl(msg);
@@ -1646,23 +1737,6 @@
     }
 
     /**
-     * Check if there is any data setup retry scheduled with specified data profile.
-     *
-     * @param dataProfile The data profile to retry.
-     * @param transport The transport that the request is on.
-     * @return {@code true} if there is retry scheduled for this data profile.
-     */
-    public boolean isAnySetupRetryScheduled(@NonNull DataProfile dataProfile,
-            @TransportType int transport) {
-        return mDataRetryEntries.stream()
-                .filter(DataSetupRetryEntry.class::isInstance)
-                .map(DataSetupRetryEntry.class::cast)
-                .anyMatch(entry -> entry.getState() == DataRetryEntry.RETRY_STATE_NOT_RETRIED
-                        && dataProfile.equals(entry.dataProfile)
-                        && entry.transport == transport);
-    }
-
-    /**
      * Check if a specific data profile is explicitly throttled by the network.
      *
      * @param dataProfile The data profile to check.
@@ -1697,13 +1771,11 @@
                         && ((DataHandoverRetryEntry) entry).dataNetwork == dataNetwork
                         && entry.getState() == DataRetryEntry.RETRY_STATE_NOT_RETRIED)
                 .forEach(entry -> entry.setState(DataRetryEntry.RETRY_STATE_CANCELLED));
-        mDataThrottlingEntries.removeIf(entry -> entry.dataNetwork == dataNetwork);
     }
 
     /**
      * Check if there is any data handover retry scheduled.
      *
-     *
      * @param dataNetwork The network network to retry handover.
      * @return {@code true} if there is retry scheduled for this network capability.
      */
diff --git a/src/java/com/android/internal/telephony/data/DataSettingsManager.java b/src/java/com/android/internal/telephony/data/DataSettingsManager.java
index f6b33ad..5178ae4 100644
--- a/src/java/com/android/internal/telephony/data/DataSettingsManager.java
+++ b/src/java/com/android/internal/telephony/data/DataSettingsManager.java
@@ -28,7 +28,6 @@
 import android.provider.Settings;
 import android.sysprop.TelephonyProperties;
 import android.telephony.CarrierConfigManager;
-import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
 import android.telephony.TelephonyManager;
 import android.telephony.TelephonyManager.MobileDataPolicy;
@@ -46,8 +45,8 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.SettingsObserver;
-import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.data.DataConfigManager.DataConfigManagerCallback;
+import com.android.internal.telephony.metrics.DeviceTelephonyPropertiesStats;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.util.TelephonyUtils;
@@ -396,16 +395,10 @@
     }
 
     private boolean isStandAloneOpportunistic(int subId) {
-        if (mPhone.isSubscriptionManagerServiceEnabled()) {
-            SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
-                    .getSubscriptionInfoInternal(subId);
-            return subInfo != null && subInfo.isOpportunistic()
-                    && TextUtils.isEmpty(subInfo.getGroupUuid());
-        }
-        SubscriptionInfo info = SubscriptionController.getInstance().getActiveSubscriptionInfo(
-                subId, mPhone.getContext().getOpPackageName(),
-                mPhone.getContext().getAttributionTag());
-        return (info != null) && info.isOpportunistic() && info.getGroupUuid() == null;
+        SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
+                .getSubscriptionInfoInternal(subId);
+        return subInfo != null && subInfo.isOpportunistic()
+                && TextUtils.isEmpty(subInfo.getGroupUuid());
     }
 
     /**
@@ -584,16 +577,11 @@
 
     /** Refresh the enabled mobile data policies from Telephony database */
     private void refreshEnabledMobileDataPolicy() {
-        if (mPhone.isSubscriptionManagerServiceEnabled()) {
-            SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
-                    .getSubscriptionInfoInternal(mSubId);
-            if (subInfo != null) {
-                mEnabledMobileDataPolicy = getMobileDataPolicyEnabled(
-                        subInfo.getEnabledMobileDataPolicies());
-            }
-        } else {
-            mEnabledMobileDataPolicy = getMobileDataPolicyEnabled(SubscriptionController
-                    .getInstance().getEnabledMobileDataPolicies(mSubId));
+        SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
+                .getSubscriptionInfoInternal(mSubId);
+        if (subInfo != null) {
+            mEnabledMobileDataPolicy = getMobileDataPolicyEnabled(
+                    subInfo.getEnabledMobileDataPolicies());
         }
     }
 
@@ -624,6 +612,8 @@
         if (enable == isMobileDataPolicyEnabled(mobileDataPolicy)) {
             return;
         }
+        metricsRecordSetMobileDataPolicy(mobileDataPolicy);
+
         if (enable) {
             mEnabledMobileDataPolicy.add(mobileDataPolicy);
         } else {
@@ -632,23 +622,21 @@
 
         String enabledMobileDataPolicies = mEnabledMobileDataPolicy.stream().map(String::valueOf)
                 .collect(Collectors.joining(","));
-        if (mPhone.isSubscriptionManagerServiceEnabled()) {
-            SubscriptionManagerService.getInstance().setEnabledMobileDataPolicies(mSubId,
-                    enabledMobileDataPolicies);
-            logl(TelephonyUtils.mobileDataPolicyToString(mobileDataPolicy) + " changed to "
-                    + enable);
-            updateDataEnabledAndNotify(TelephonyManager.DATA_ENABLED_REASON_OVERRIDE);
-            notifyDataEnabledOverrideChanged(enable, mobileDataPolicy);
-        } else {
-            if (SubscriptionController.getInstance().setEnabledMobileDataPolicies(
-                    mSubId, enabledMobileDataPolicies)) {
-                logl(TelephonyUtils.mobileDataPolicyToString(mobileDataPolicy) + " changed to "
-                        + enable);
-                updateDataEnabledAndNotify(TelephonyManager.DATA_ENABLED_REASON_OVERRIDE);
-                notifyDataEnabledOverrideChanged(enable, mobileDataPolicy);
-            } else {
-                loge("onSetMobileDataPolicy: failed to set " + enabledMobileDataPolicies);
-            }
+        SubscriptionManagerService.getInstance().setEnabledMobileDataPolicies(mSubId,
+                enabledMobileDataPolicies);
+        logl(TelephonyUtils.mobileDataPolicyToString(mobileDataPolicy) + " changed to "
+                + enable);
+        updateDataEnabledAndNotify(TelephonyManager.DATA_ENABLED_REASON_OVERRIDE);
+        notifyDataEnabledOverrideChanged(enable, mobileDataPolicy);
+    }
+
+    /**
+     * Record the number of times a mobile data policy is toggled to metrics.
+     * @param mobileDataPolicy The mobile data policy that's toggled
+     */
+    private void metricsRecordSetMobileDataPolicy(@MobileDataPolicy int mobileDataPolicy) {
+        if (mobileDataPolicy == TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH) {
+            DeviceTelephonyPropertiesStats.recordAutoDataSwitchFeatureToggle();
         }
     }
 
@@ -732,38 +720,25 @@
             overridden = apnType == ApnSetting.TYPE_MMS;
         }
 
-        boolean isNonDds;
-        if (mPhone.isSubscriptionManagerServiceEnabled()) {
-            isNonDds = mPhone.getSubId() != SubscriptionManagerService.getInstance()
-                    .getDefaultDataSubId();
-        } else {
-            isNonDds = mPhone.getSubId() != SubscriptionController.getInstance()
-                    .getDefaultDataSubId();
-        }
+        boolean isNonDds = mPhone.getSubId() != SubscriptionManagerService.getInstance()
+                .getDefaultDataSubId();
 
         // mobile data policy : data during call
         if (isMobileDataPolicyEnabled(TelephonyManager
                 .MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL)) {
-            overridden = isNonDds && mPhone.getState() != PhoneConstants.State.IDLE;
+            overridden = overridden || isNonDds && mPhone.getState() != PhoneConstants.State.IDLE;
         }
 
         // mobile data policy : auto data switch
         if (isMobileDataPolicyEnabled(TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH)) {
-            Phone defaultDataPhone;
-            if (mPhone.isSubscriptionManagerServiceEnabled()) {
-                // check user enabled data on the default data phone
-                defaultDataPhone = PhoneFactory.getPhone(SubscriptionManagerService.getInstance()
-                        .getPhoneId(SubscriptionManagerService.getInstance()
-                                .getDefaultDataSubId()));
-            } else {
-                // check user enabled data on the default data phone
-                defaultDataPhone = PhoneFactory.getPhone(SubscriptionController.getInstance()
-                        .getPhoneId(SubscriptionController.getInstance().getDefaultDataSubId()));
-            }
+            // check user enabled data on the default data phone
+            Phone defaultDataPhone = PhoneFactory.getPhone(SubscriptionManagerService.getInstance()
+                    .getPhoneId(SubscriptionManagerService.getInstance()
+                            .getDefaultDataSubId()));
             if (defaultDataPhone == null) {
                 loge("isDataEnabledOverriddenForApn: unexpected defaultDataPhone is null");
             } else {
-                overridden = isNonDds && defaultDataPhone.isUserDataEnabled();
+                overridden = overridden || isNonDds && defaultDataPhone.isUserDataEnabled();
             }
         }
         return overridden;
diff --git a/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java b/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java
index f5b60e1..6c6f064 100644
--- a/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java
+++ b/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony.data;
 
+import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
+
 import android.annotation.CallbackExecutor;
 import android.annotation.ElapsedRealtimeLong;
 import android.annotation.IntDef;
@@ -32,7 +34,6 @@
 import android.telephony.CellSignalStrength;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
-import android.telephony.data.DataProfile;
 import android.util.IndentingPrintWriter;
 import android.util.LocalLog;
 
@@ -267,7 +268,7 @@
 
                     @Override
                     public void onInternetDataNetworkConnected(
-                            @NonNull List<DataProfile> dataProfiles) {
+                            @NonNull List<DataNetwork> internetNetworks) {
                         mIsInternetNetworkConnected = true;
                         logl("onInternetDataNetworkConnected");
                     }
@@ -489,7 +490,7 @@
         Intent intent = new Intent(TelephonyManager.ACTION_DATA_STALL_DETECTED);
         SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
         intent.putExtra(TelephonyManager.EXTRA_RECOVERY_ACTION, recoveryAction);
-        mPhone.getContext().sendBroadcast(intent);
+        mPhone.getContext().sendBroadcast(intent, READ_PRIVILEGED_PHONE_STATE);
     }
 
     /** Recovery Action: RECOVERY_ACTION_GET_DATA_CALL_LIST */
@@ -640,7 +641,10 @@
 
         if (isLogNeeded) {
             timeDurationOfCurrentAction =
-                (isFirstDataStall == true ? 0 : (int) getDurationOfCurrentRecoveryMs());
+                ((getRecoveryAction() > RECOVERY_ACTION_GET_DATA_CALL_LIST
+                   && !mIsAttemptedAllSteps)
+                 || mLastAction == RECOVERY_ACTION_RESET_MODEM)
+                 ? (int) getDurationOfCurrentRecoveryMs() : 0;
             DataStallRecoveryStats.onDataStallEvent(
                     mLastAction, mPhone, isValid, timeDuration, reason,
                     isFirstValidationAfterDoRecovery, timeDurationOfCurrentAction);
diff --git a/src/java/com/android/internal/telephony/data/LinkBandwidthEstimator.java b/src/java/com/android/internal/telephony/data/LinkBandwidthEstimator.java
index 5ceb5e4..5ed12aa 100644
--- a/src/java/com/android/internal/telephony/data/LinkBandwidthEstimator.java
+++ b/src/java/com/android/internal/telephony/data/LinkBandwidthEstimator.java
@@ -458,6 +458,23 @@
         long txBytesDelta = mobileTxBytes - mLastMobileTxBytes;
         long rxBytesDelta = mobileRxBytes - mLastMobileRxBytes;
 
+        int dataActivity;
+        if (txBytesDelta > 0 && rxBytesDelta > 0) {
+            dataActivity = TelephonyManager.DATA_ACTIVITY_INOUT;
+        } else if (rxBytesDelta > 0) {
+            dataActivity = TelephonyManager.DATA_ACTIVITY_IN;
+        } else if (txBytesDelta > 0) {
+            dataActivity = TelephonyManager.DATA_ACTIVITY_OUT;
+        } else {
+            dataActivity = TelephonyManager.DATA_ACTIVITY_NONE;
+        }
+
+        if (mDataActivity != dataActivity) {
+            mDataActivity = dataActivity;
+            mLinkBandwidthEstimatorCallbacks.forEach(callback -> callback.invokeFromExecutor(
+                    () -> callback.onDataActivityChanged(dataActivity)));
+        }
+
         // Schedule the next traffic stats poll
         sendEmptyMessageDelayed(MSG_TRAFFIC_STATS_POLL, TRAFFIC_STATS_POLL_INTERVAL_MS);
 
@@ -506,23 +523,6 @@
             return;
         }
 
-        int dataActivity;
-        if (txBytesDelta > 0 && rxBytesDelta > 0) {
-            dataActivity = TelephonyManager.DATA_ACTIVITY_INOUT;
-        } else if (rxBytesDelta > 0) {
-            dataActivity = TelephonyManager.DATA_ACTIVITY_IN;
-        } else if (txBytesDelta > 0) {
-            dataActivity = TelephonyManager.DATA_ACTIVITY_OUT;
-        } else {
-            dataActivity = TelephonyManager.DATA_ACTIVITY_NONE;
-        }
-
-        if (mDataActivity != dataActivity) {
-            mDataActivity = dataActivity;
-            mLinkBandwidthEstimatorCallbacks.forEach(callback -> callback.invokeFromExecutor(
-                    () -> callback.onDataActivityChanged(dataActivity)));
-        }
-
         long timeSinceLastFilterUpdateMs = currTimeMs - mFilterUpdateTimeMs;
         // Update filter
         if (timeSinceLastFilterUpdateMs >= FILTER_UPDATE_MAX_INTERVAL_MS) {
@@ -678,7 +678,7 @@
                 return;
             }
             int linkBandwidthKbps = (int) linkBandwidthLongKbps;
-            mBwSampleValid = true;
+            mBwSampleValid = linkBandwidthKbps > 0;
             mBwSampleKbps = linkBandwidthKbps;
 
             String dataRatName = getDataRatName(mDataRat);
diff --git a/src/java/com/android/internal/telephony/data/PhoneSwitcher.java b/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
index 1ff7fde..5e13f6b 100644
--- a/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
+++ b/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
@@ -86,8 +86,6 @@
 import com.android.internal.telephony.PhoneConfigurationManager;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.RadioConfig;
-import com.android.internal.telephony.SubscriptionController;
-import com.android.internal.telephony.SubscriptionController.WatchedInt;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.data.DataNetworkController.NetworkRequestList;
 import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback;
@@ -97,6 +95,7 @@
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent.OnDemandDataSwitch;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
+import com.android.internal.telephony.subscription.SubscriptionManagerService.WatchedInt;
 import com.android.internal.telephony.util.NotificationChannelController;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.telephony.Rlog;
@@ -206,7 +205,6 @@
 
     private final @NonNull NetworkRequestList mNetworkRequestList = new NetworkRequestList();
     protected final RegistrantList mActivePhoneRegistrants;
-    protected final SubscriptionController mSubscriptionController;
     private final SubscriptionManagerService mSubscriptionManagerService;
     protected final Context mContext;
     private final LocalLog mLocalLog;
@@ -276,14 +274,8 @@
     protected int mPreferredDataPhoneId = SubscriptionManager.INVALID_PHONE_INDEX;
 
     // Subscription ID corresponds to mPreferredDataPhoneId.
-    protected WatchedInt mPreferredDataSubId =
-            new WatchedInt(SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
-        @Override
-        public void set(int newValue) {
-            super.set(newValue);
-            SubscriptionManager.invalidateActiveDataSubIdCaches();
-        }
-    };
+    protected WatchedInt mPreferredDataSubId = new WatchedInt(
+            SubscriptionManager.INVALID_SUBSCRIPTION_ID);
 
     // If non-null, An emergency call is about to be started, is ongoing, or has just ended and we
     // are overriding the DDS.
@@ -292,18 +284,8 @@
 
     private ISetOpportunisticDataCallback mSetOpptSubCallback;
 
-    /** Data config manager callback for updating device config. **/
-    private final DataConfigManager.DataConfigManagerCallback mDataConfigManagerCallback =
-            new DataConfigManager.DataConfigManagerCallback(this::post) {
-        @Override
-        public void onDeviceConfigChanged() {
-            log("onDeviceConfigChanged");
-            PhoneSwitcher.this.updateConfig();
-        }
-    };
-
     private static final int EVENT_PRIMARY_DATA_SUB_CHANGED       = 101;
-    protected static final int EVENT_SUBSCRIPTION_CHANGED           = 102;
+    protected static final int EVENT_SUBSCRIPTION_CHANGED         = 102;
     private static final int EVENT_REQUEST_NETWORK                = 103;
     private static final int EVENT_RELEASE_NETWORK                = 104;
     // ECBM has started/ended. If we just ended an emergency call and mEmergencyOverride is not
@@ -362,6 +344,12 @@
     private List<Set<CommandException.Error>> mCurrentDdsSwitchFailure;
 
     /**
+     * {@code true} if requires ping test before switching preferred data modem; otherwise, switch
+     * even if ping test fails.
+     */
+    private boolean mRequirePingTestBeforeDataSwitch = true;
+
+    /**
      * 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.
@@ -371,8 +359,7 @@
     /**
      * The maximum number of retries when a validation for switching failed.
      */
-    private int mAutoDataSwitchValidationMaxRetry =
-            DataConfigManager.DEFAULT_AUTO_DATA_SWITCH_MAX_RETRY;
+    private int mAutoDataSwitchValidationMaxRetry;
 
     /** Data settings manager callback. Key is the phone id. */
     private final @NonNull Map<Integer, DataSettingsManagerCallback> mDataSettingsManagerCallbacks =
@@ -464,7 +451,6 @@
     public static PhoneSwitcher make(int maxDataAttachModemCount, Context context, Looper looper) {
         if (sPhoneSwitcher == null) {
             sPhoneSwitcher = new PhoneSwitcher(maxDataAttachModemCount, context, looper);
-            SubscriptionManager.invalidateActiveDataSubIdCaches();
         }
 
         return sPhoneSwitcher;
@@ -531,13 +517,7 @@
         mMaxDataAttachModemCount = maxActivePhones;
         mLocalLog = new LocalLog(MAX_LOCAL_LOG_LINES);
 
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            mSubscriptionManagerService = SubscriptionManagerService.getInstance();
-            mSubscriptionController = null;
-        } else {
-            mSubscriptionController = SubscriptionController.getInstance();
-            mSubscriptionManagerService = null;
-        }
+        mSubscriptionManagerService = SubscriptionManagerService.getInstance();
 
         mRadioConfig = RadioConfig.getInstance();
         mValidator = CellularNetworkValidator.getInstance();
@@ -583,6 +563,8 @@
             PhoneFactory.getPhone(0).mCi.registerForOn(this, EVENT_RADIO_ON, null);
         }
 
+        readDeviceResourceConfig();
+
         TelephonyRegistryManager telephonyRegistryManager = (TelephonyRegistryManager)
                 context.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
         telephonyRegistryManager.addOnSubscriptionsChangedListener(
@@ -663,16 +645,9 @@
             return false;
         }
 
-        SubscriptionInfo info;
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            info = mSubscriptionManagerService
-                    .getActiveSubscriptionInfoForSimSlotIndex(slotIndex,
-                            mContext.getOpPackageName(), mContext.getAttributionTag());
-        } else {
-            info = mSubscriptionController
-                    .getActiveSubscriptionInfoForSimSlotIndex(slotIndex,
-                            mContext.getOpPackageName(), null);
-        }
+        SubscriptionInfo info = mSubscriptionManagerService
+                .getActiveSubscriptionInfoForSimSlotIndex(slotIndex,
+                        mContext.getOpPackageName(), mContext.getAttributionTag());
         boolean uiccAppsEnabled = info != null && info.areUiccApplicationsEnabled();
 
         IccCard iccCard = PhoneFactory.getPhone(slotIndex).getIccCard();
@@ -840,6 +815,9 @@
             }
             case EVENT_MODEM_COMMAND_RETRY: {
                 int phoneId = (int) msg.obj;
+                if (mActiveModemCount <= phoneId) {
+                    break;
+                }
                 if (isPhoneIdValidForRetry(phoneId)) {
                     logl("EVENT_MODEM_COMMAND_RETRY: resend modem command on phone " + phoneId);
                     sendRilCommands(phoneId);
@@ -905,50 +883,40 @@
                 break;
             }
             case EVENT_PROCESS_SIM_STATE_CHANGE: {
-                int slotIndex = (int) msg.arg1;
-                int simState = (int) msg.arg2;
+                int slotIndex = msg.arg1;
+                int simState = msg.arg2;
 
                 if (!SubscriptionManager.isValidSlotIndex(slotIndex)) {
                     logl("EVENT_PROCESS_SIM_STATE_CHANGE: skip processing due to invalid slotId: "
                             + slotIndex);
-                } else if (mCurrentDdsSwitchFailure.get(slotIndex).contains(
+                } else if (TelephonyManager.SIM_STATE_LOADED == simState) {
+                    if (mCurrentDdsSwitchFailure.get(slotIndex).contains(
                         CommandException.Error.INVALID_SIM_STATE)
                         && (TelephonyManager.SIM_STATE_LOADED == simState)
                         && isSimApplicationReady(slotIndex)) {
-                    sendRilCommands(slotIndex);
+                        sendRilCommands(slotIndex);
+                    }
+                    // SIM loaded after subscriptions slot mapping are done. Evaluate for auto
+                    // data switch.
+                    sendEmptyMessage(EVENT_EVALUATE_AUTO_SWITCH);
                 }
-
-                registerConfigChange();
                 break;
             }
         }
     }
 
     /**
-     * Register for device config change on the primary data phone.
+     * Read the default device config from any default phone because the resource config are per
+     * device. No need to register callback for the same reason.
      */
-    private void registerConfigChange() {
-        Phone phone = getPhoneBySubId(mPrimaryDataSubId);
-        if (phone != null) {
-            DataConfigManager dataConfig = phone.getDataNetworkController().getDataConfigManager();
-            dataConfig.registerCallback(mDataConfigManagerCallback);
-            updateConfig();
-            sendEmptyMessage(EVENT_EVALUATE_AUTO_SWITCH);
-        }
-    }
-
-    /**
-     * Update data config.
-     */
-    private void updateConfig() {
-        Phone phone = getPhoneBySubId(mPrimaryDataSubId);
-        if (phone != null) {
-            DataConfigManager dataConfig = phone.getDataNetworkController().getDataConfigManager();
-            mAutoDataSwitchAvailabilityStabilityTimeThreshold =
-                    dataConfig.getAutoDataSwitchAvailabilityStabilityTimeThreshold();
-            mAutoDataSwitchValidationMaxRetry =
-                    dataConfig.getAutoDataSwitchValidationMaxRetry();
-        }
+    private void readDeviceResourceConfig() {
+        Phone phone = PhoneFactory.getDefaultPhone();
+        DataConfigManager dataConfig = phone.getDataNetworkController().getDataConfigManager();
+        mRequirePingTestBeforeDataSwitch = dataConfig.isPingTestBeforeAutoDataSwitchRequired();
+        mAutoDataSwitchAvailabilityStabilityTimeThreshold =
+                dataConfig.getAutoDataSwitchAvailabilityStabilityTimeThreshold();
+        mAutoDataSwitchValidationMaxRetry =
+                dataConfig.getAutoDataSwitchValidationMaxRetry();
     }
 
     private synchronized void onMultiSimConfigChanged(int activeModemCount) {
@@ -1129,16 +1097,9 @@
         // auto data switch feature is disabled from server
         if (mAutoDataSwitchAvailabilityStabilityTimeThreshold < 0) return;
         // check is valid DSDS
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            if (!isActiveSubId(mPrimaryDataSubId) || mSubscriptionManagerService
-                    .getActiveSubIdList(true).length <= 1) {
-                return;
-            }
-        } else {
-            if (!isActiveSubId(mPrimaryDataSubId)
-                    || mSubscriptionController.getActiveSubIdList(true).length <= 1) {
-                return;
-            }
+        if (!isActiveSubId(mPrimaryDataSubId) || mSubscriptionManagerService
+                .getActiveSubIdList(true).length <= 1) {
+            return;
         }
 
         Phone primaryDataPhone = getPhoneBySubId(mPrimaryDataSubId);
@@ -1158,7 +1119,7 @@
 
             int candidateSubId = getAutoSwitchTargetSubIdIfExists();
             if (candidateSubId != INVALID_SUBSCRIPTION_ID) {
-                startAutoDataSwitchStabilityCheck(candidateSubId, true);
+                startAutoDataSwitchStabilityCheck(candidateSubId, mRequirePingTestBeforeDataSwitch);
             } else {
                 cancelPendingAutoDataSwitch();
             }
@@ -1193,7 +1154,8 @@
 
             if (isInService(mPhoneStates[primaryPhoneId])) {
                 // primary becomes available
-                startAutoDataSwitchStabilityCheck(DEFAULT_SUBSCRIPTION_ID, true);
+                startAutoDataSwitchStabilityCheck(DEFAULT_SUBSCRIPTION_ID,
+                        mRequirePingTestBeforeDataSwitch);
                 return;
             }
 
@@ -1317,12 +1279,7 @@
         boolean diffDetected = mHalCommandToUse != HAL_COMMAND_PREFERRED_DATA && requestsChanged;
 
         // Check if user setting of default non-opportunistic data sub is changed.
-        int primaryDataSubId;
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            primaryDataSubId = mSubscriptionManagerService.getDefaultDataSubId();
-        } else {
-            primaryDataSubId = mSubscriptionController.getDefaultDataSubId();
-        }
+        int primaryDataSubId = mSubscriptionManagerService.getDefaultDataSubId();
         if (primaryDataSubId != mPrimaryDataSubId) {
             sb.append(" mPrimaryDataSubId ").append(mPrimaryDataSubId).append("->")
                 .append(primaryDataSubId);
@@ -1425,7 +1382,7 @@
                     }
 
                     if (newActivePhones.size() < mMaxDataAttachModemCount
-                            && newActivePhones.contains(mPreferredDataPhoneId)
+                            && !newActivePhones.contains(mPreferredDataPhoneId)
                             && SubscriptionManager.isUsableSubIdValue(mPreferredDataPhoneId)) {
                         newActivePhones.add(mPreferredDataPhoneId);
                     }
@@ -1597,13 +1554,9 @@
     }
 
     private boolean isActiveSubId(int subId) {
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            SubscriptionInfoInternal subInfo = mSubscriptionManagerService
-                    .getSubscriptionInfoInternal(subId);
-            return subInfo != null && subInfo.isActive();
-        } else {
-            return mSubscriptionController.isActiveSubId(subId);
-        }
+        SubscriptionInfoInternal subInfo = mSubscriptionManagerService
+                .getSubscriptionInfoInternal(subId);
+        return subInfo != null && subInfo.isActive();
     }
 
     // This updates mPreferredDataPhoneId which decides which phone should handle default network
@@ -1686,11 +1639,7 @@
     }
 
     private Phone getPhoneBySubId(int subId) {
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            return findPhoneById(mSubscriptionManagerService.getPhoneId(subId));
-        } else {
-            return findPhoneById(mSubscriptionController.getPhoneId(subId));
-        }
+        return findPhoneById(mSubscriptionManagerService.getPhoneId(subId));
     }
 
     private Phone findPhoneById(final int phoneId) {
@@ -2067,8 +2016,8 @@
      * @param reason The switching reason.
      */
     private void logDataSwitchEvent(int subId, int state, int reason) {
-        logl("Data switch event. subId=" + subId + ", state=" + switchStateToString(state)
-                + ", reason=" + switchReasonToString(reason));
+        logl("Data switch state=" + switchStateToString(state) + " due to reason="
+                + switchReasonToString(reason) + " on subId " + subId);
         DataSwitch dataSwitch = new DataSwitch();
         dataSwitch.state = state;
         dataSwitch.reason = reason;
@@ -2121,15 +2070,9 @@
         }
         pw.println("mPreferredDataPhoneId=" + mPreferredDataPhoneId);
         pw.println("mPreferredDataSubId=" + mPreferredDataSubId.get());
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            pw.println("DefaultDataSubId=" + mSubscriptionManagerService.getDefaultDataSubId());
-            pw.println("DefaultDataPhoneId=" + mSubscriptionManagerService.getPhoneId(
-                    mSubscriptionManagerService.getDefaultDataSubId()));
-        } else {
-            pw.println("DefaultDataSubId=" + mSubscriptionController.getDefaultDataSubId());
-            pw.println("DefaultDataPhoneId=" + mSubscriptionController.getPhoneId(
-                    mSubscriptionController.getDefaultDataSubId()));
-        }
+        pw.println("DefaultDataSubId=" + mSubscriptionManagerService.getDefaultDataSubId());
+        pw.println("DefaultDataPhoneId=" + mSubscriptionManagerService.getPhoneId(
+                mSubscriptionManagerService.getDefaultDataSubId()));
         pw.println("mPrimaryDataSubId=" + mPrimaryDataSubId);
         pw.println("mAutoSelectedDataSubId=" + mAutoSelectedDataSubId);
         pw.println("mIsRegisteredForImsRadioTechChange=" + mIsRegisteredForImsRadioTechChange);
@@ -2141,6 +2084,7 @@
         pw.println("mAutoDataSwitchAvailabilityStabilityTimeThreshold="
                 + mAutoDataSwitchAvailabilityStabilityTimeThreshold);
         pw.println("mAutoDataSwitchValidationMaxRetry=" + mAutoDataSwitchValidationMaxRetry);
+        pw.println("mRequirePingTestBeforeDataSwitch=" + mRequirePingTestBeforeDataSwitch);
         pw.println("mLastSwitchPreferredDataReason="
                 + switchReasonToString(mLastSwitchPreferredDataReason));
         pw.println("mDisplayedAutoSwitchNotification=" + mDisplayedAutoSwitchNotification);
@@ -2215,12 +2159,8 @@
                     + switchReasonToString(mLastSwitchPreferredDataReason));
             return;
         }
-        SubscriptionInfo subInfo;
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            subInfo = mSubscriptionManagerService.getSubscriptionInfo(mAutoSelectedDataSubId);
-        } else {
-            subInfo = mSubscriptionController.getSubscriptionInfo(mAutoSelectedDataSubId);
-        }
+        SubscriptionInfo subInfo = mSubscriptionManagerService
+                .getSubscriptionInfo(mAutoSelectedDataSubId);
         if (subInfo == null || subInfo.isOpportunistic()) {
             loge("displayAutoDataSwitchNotification: mAutoSelectedDataSubId="
                     + mAutoSelectedDataSubId + " unexpected subInfo " + subInfo);
@@ -2259,14 +2199,8 @@
     }
 
     private boolean isPhoneIdValidForRetry(int phoneId) {
-        int ddsPhoneId;
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            ddsPhoneId = mSubscriptionManagerService.getPhoneId(
-                    mSubscriptionManagerService.getDefaultDataSubId());
-        } else {
-            ddsPhoneId = mSubscriptionController.getPhoneId(
-                    mSubscriptionController.getDefaultDataSubId());
-        }
+        int ddsPhoneId = mSubscriptionManagerService.getPhoneId(
+                mSubscriptionManagerService.getDefaultDataSubId());
         if (ddsPhoneId != INVALID_PHONE_INDEX && ddsPhoneId == phoneId) {
             return true;
         } else {
diff --git a/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java
new file mode 100644
index 0000000..9a75b43
--- /dev/null
+++ b/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java
@@ -0,0 +1,467 @@
+/*
+ * Copyright (C) 2022 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 android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.AsyncResult;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
+import android.telephony.Annotation.DisconnectCauses;
+import android.telephony.DomainSelectionService;
+import android.telephony.DomainSelectionService.EmergencyScanType;
+import android.telephony.DomainSelector;
+import android.telephony.EmergencyRegResult;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.TransportSelectorCallback;
+import android.telephony.WwanSelectorCallback;
+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.Phone;
+import com.android.internal.telephony.util.TelephonyUtils;
+
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.function.Consumer;
+
+
+/**
+ * Manages the information of request and the callback binder.
+ */
+public class DomainSelectionConnection {
+
+    private static final boolean DBG = TelephonyUtils.IS_DEBUGGABLE;
+
+    protected static final int EVENT_EMERGENCY_NETWORK_SCAN_RESULT = 1;
+    protected static final int EVENT_QUALIFIED_NETWORKS_CHANGED = 2;
+
+    /** Callback to receive responses from DomainSelectionConnection. */
+    public interface DomainSelectionConnectionCallback {
+        /**
+         * Notifies that selection has terminated because there is no decision that can be made
+         * or a timeout has occurred. The call should be terminated when this method is called.
+         *
+         * @param cause Indicates the reason.
+         */
+        void onSelectionTerminated(@DisconnectCauses int cause);
+    }
+
+    /** An internal class implementing {@link TransportSelectorCallback} interface. */
+    private final class TransportSelectorCallbackWrapper implements TransportSelectorCallback {
+        @Override
+        public void onCreated(@NonNull DomainSelector selector) {
+            mDomainSelector = selector;
+            DomainSelectionConnection.this.onCreated();
+        }
+
+        @Override
+        public void onWlanSelected(boolean useEmergencyPdn) {
+            DomainSelectionConnection.this.onWlanSelected(useEmergencyPdn);
+        }
+
+        @Override
+        public @NonNull WwanSelectorCallback onWwanSelected() {
+            if (mWwanSelectorCallback == null) {
+                mWwanSelectorCallback = new WwanSelectorCallbackWrapper();
+            }
+            DomainSelectionConnection.this.onWwanSelected();
+            return mWwanSelectorCallback;
+        }
+
+        @Override
+        public void onWwanSelected(final Consumer<WwanSelectorCallback> consumer) {
+            if (mWwanSelectorCallback == null) {
+                mWwanSelectorCallback = new WwanSelectorCallbackWrapper();
+            }
+            if (mWwanSelectedExecutor == null) {
+                mWwanSelectedExecutor = Executors.newSingleThreadExecutor();
+            }
+            mWwanSelectedExecutor.execute(() -> {
+                DomainSelectionConnection.this.onWwanSelected();
+                consumer.accept(mWwanSelectorCallback);
+            });
+        }
+
+        @Override
+        public void onSelectionTerminated(int cause) {
+            DomainSelectionConnection.this.onSelectionTerminated(cause);
+            dispose();
+        }
+    }
+
+    /** An internal class implementing {@link WwanSelectorCallback} interface. */
+    private final class WwanSelectorCallbackWrapper
+            implements WwanSelectorCallback, CancellationSignal.OnCancelListener {
+        @Override
+        public void onRequestEmergencyNetworkScan(@NonNull List<Integer> preferredNetworks,
+                @EmergencyScanType int scanType, @NonNull CancellationSignal signal,
+                @NonNull Consumer<EmergencyRegResult> consumer) {
+            if (signal != null) signal.setOnCancelListener(this);
+            mResultCallback = consumer;
+            initHandler();
+            DomainSelectionConnection.this.onRequestEmergencyNetworkScan(
+                    preferredNetworks.stream().mapToInt(Integer::intValue).toArray(), scanType);
+        }
+
+        @Override
+        public void onDomainSelected(@NetworkRegistrationInfo.Domain int domain,
+                boolean useEmergencyPdn) {
+            DomainSelectionConnection.this.onDomainSelected(domain, useEmergencyPdn);
+        }
+
+        @Override
+        public void onCancel() {
+            DomainSelectionConnection.this.onCancel();
+        }
+    }
+
+    protected final class DomainSelectionConnectionHandler extends Handler {
+        DomainSelectionConnectionHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            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;
+                    if (DBG) logd("EVENT_EMERGENCY_NETWORK_SCAN_RESULT result=" + regResult);
+                    CompletableFuture.runAsync(
+                            () -> mResultCallback.accept(regResult),
+                            mController.getDomainSelectionServiceExecutor()).join();
+                    break;
+                case EVENT_QUALIFIED_NETWORKS_CHANGED:
+                    onQualifiedNetworksChanged();
+                    break;
+                default:
+                    loge("handleMessage unexpected msg=" + msg.what);
+                    break;
+            }
+        }
+    }
+
+    protected String mTag = "DomainSelectionConnection";
+
+    private final LocalLog mLocalLog = new LocalLog(30);
+    private final @NonNull TransportSelectorCallback mTransportSelectorCallback;
+
+    /**
+     * Controls the communication between {@link DomainSelectionConnection} and
+     * {@link DomainSelectionService}.
+     */
+    private final @NonNull DomainSelectionController mController;
+    /** Indicates whether the requested service is for emergency services. */
+    private final boolean mIsEmergency;
+
+    /** Interface to receive the request to trigger emergency network scan and selected domain. */
+    private @Nullable WwanSelectorCallback mWwanSelectorCallback;
+    /** Interface to return the result of emergency network scan. */
+    private @Nullable Consumer<EmergencyRegResult> mResultCallback;
+    /** Interface to the {@link DomainSelector} created for this service. */
+    private @Nullable DomainSelector mDomainSelector;
+
+    /** The slot requested this connection. */
+    protected @NonNull Phone mPhone;
+    /** The requested domain selector type. */
+    private @DomainSelectionService.SelectorType int mSelectorType;
+
+    /** The attributes required to determine the domain. */
+    private @Nullable DomainSelectionService.SelectionAttributes mSelectionAttributes;
+
+    private @Nullable Looper mLooper;
+    protected @Nullable DomainSelectionConnectionHandler mHandler;
+    private boolean mRegisteredRegistrant;
+    private boolean mIsWaitingForScanResult;
+
+    private @NonNull AndroidFuture<Integer> mOnComplete;
+
+    private @Nullable Executor mWwanSelectedExecutor;
+
+    /**
+     * Creates an instance.
+     *
+     * @param phone For which this service is requested.
+     * @param selectorType Indicates the type of the requested service.
+     * @param isEmergency Indicates whether this request is for emergency service.
+     * @param controller The controller to communicate with the domain selection service.
+     */
+    public DomainSelectionConnection(@NonNull Phone phone,
+            @DomainSelectionService.SelectorType int selectorType, boolean isEmergency,
+            @NonNull DomainSelectionController controller) {
+        mController = controller;
+        mPhone = phone;
+        mSelectorType = selectorType;
+        mIsEmergency = isEmergency;
+
+        mTransportSelectorCallback = new TransportSelectorCallbackWrapper();
+        mOnComplete = new AndroidFuture<>();
+    }
+
+    /**
+     * Returns the attributes required to determine the domain for a telephony service.
+     *
+     * @return The attributes required to determine the domain.
+     */
+    public @Nullable DomainSelectionService.SelectionAttributes getSelectionAttributes() {
+        return mSelectionAttributes;
+    }
+
+    /**
+     * Returns the interface for the callbacks.
+     *
+     * @return The {@link TransportSelectorCallback} interface.
+     */
+    @VisibleForTesting
+    public @NonNull TransportSelectorCallback getTransportSelectorCallback() {
+        return mTransportSelectorCallback;
+    }
+
+    /**
+     * Returns the {@link CompletableFuture} to receive the selected domain.
+     *
+     * @return The callback to receive response.
+     */
+    public @NonNull CompletableFuture<Integer> getCompletableFuture() {
+        return mOnComplete;
+    }
+
+    /**
+     * Returs the {@link Phone} which requested this connection.
+     *
+     * @return The {@link Phone} instance.
+     */
+    public @NonNull Phone getPhone() {
+        return mPhone;
+    }
+
+    /**
+     * Requests the domain selection servic 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());
+    }
+
+    /**
+     * Notifies that {@link DomainSelector} instance has been created for the selection request.
+     */
+    public void onCreated() {
+        // Can be overridden if required
+    }
+
+    /**
+     * Notifies that WLAN transport has been selected.
+     */
+    public void onWlanSelected() {
+        // Can be overridden.
+    }
+
+    /**
+     * Notifies that WLAN transport has been selected.
+     *
+     * @param useEmergencyPdn Indicates whether Wi-Fi emergency services use emergency PDN or not.
+     */
+    public void onWlanSelected(boolean useEmergencyPdn) {
+        // Can be overridden.
+        onWlanSelected();
+    }
+
+    /**
+     * Notifies that WWAN transport has been selected.
+     */
+    public void onWwanSelected() {
+        // Can be overridden.
+    }
+
+    /**
+     * Notifies that selection has terminated because there is no decision that can be made
+     * or a timeout has occurred. The call should be terminated when this method is called.
+     *
+     * @param cause Indicates the reason.
+     */
+    public void onSelectionTerminated(@DisconnectCauses int cause) {
+        // Can be overridden.
+    }
+
+    /**
+     * Requests the emergency network scan.
+     *
+     * @param preferredNetworks The ordered list of preferred networks to scan.
+     * @param scanType Indicates the scan preference, such as full service or limited service.
+     */
+    public void onRequestEmergencyNetworkScan(
+            @NonNull @RadioAccessNetworkType int[] preferredNetworks,
+            @EmergencyScanType int scanType) {
+        // Can be overridden if required
+        if (!mRegisteredRegistrant) {
+            mPhone.registerForEmergencyNetworkScan(mHandler,
+                    EVENT_EMERGENCY_NETWORK_SCAN_RESULT, null);
+            mRegisteredRegistrant = true;
+        }
+        mIsWaitingForScanResult = true;
+        mPhone.triggerEmergencyNetworkScan(preferredNetworks, scanType, null);
+    }
+
+    /**
+     * Notifies the domain selected.
+     *
+     * @param domain The selected domain.
+     */
+    public void onDomainSelected(@NetworkRegistrationInfo.Domain int domain) {
+        // Can be overridden if required
+        CompletableFuture<Integer> future = getCompletableFuture();
+        future.complete(domain);
+    }
+
+    /**
+     * Notifies the domain selected.
+     *
+     * @param domain The selected domain.
+     * @param useEmergencyPdn Indicates whether emergency services use emergency PDN or not.
+     */
+    public void onDomainSelected(@NetworkRegistrationInfo.Domain int domain,
+            boolean useEmergencyPdn) {
+        // Can be overridden if required
+        onDomainSelected(domain);
+    }
+
+    /**
+     * Notifies that the emergency network scan is canceled.
+     */
+    public void onCancel() {
+        // Can be overridden if required
+        onCancel(false);
+    }
+
+    private void onCancel(boolean resetScan) {
+        if (mIsWaitingForScanResult) {
+            mIsWaitingForScanResult = false;
+            mPhone.cancelEmergencyNetworkScan(resetScan, null);
+        }
+    }
+
+    /**
+     * Cancels an ongoing selection operation. It is up to the {@link DomainSelectionService}
+     * to clean up all ongoing operations with the framework.
+     */
+    public void cancelSelection() {
+        if (mDomainSelector == null) return;
+        mDomainSelector.cancelSelection();
+        dispose();
+    }
+
+    /**
+     * Requests the domain selection service to reselect a domain.
+     *
+     * @param attr The attributes required to determine the domain.
+     * @return The callback to receive the response.
+     */
+    public @NonNull CompletableFuture<Integer> reselectDomain(
+            @NonNull DomainSelectionService.SelectionAttributes attr) {
+        mSelectionAttributes = attr;
+        if (mDomainSelector == null) return null;
+        mOnComplete = new AndroidFuture<>();
+        mDomainSelector.reselectDomain(attr);
+        return mOnComplete;
+    }
+
+    /**
+     * Finishes the selection procedure and cleans everything up.
+     */
+    public void finishSelection() {
+        if (mDomainSelector == null) return;
+        mDomainSelector.finishSelection();
+        dispose();
+    }
+
+    /** Indicates that the service connection has been removed. */
+    public void onServiceDisconnected() {
+        // Can be overridden.
+        dispose();
+    }
+
+    private void dispose() {
+        if (mRegisteredRegistrant) {
+            mPhone.unregisterForEmergencyNetworkScan(mHandler);
+            mRegisteredRegistrant = false;
+        }
+        onCancel(true);
+        mController.removeConnection(this);
+        if (mLooper != null) mLooper.quitSafely();
+        mLooper = null;
+        mHandler = null;
+    }
+
+    protected void initHandler() {
+        if (mLooper == null) {
+            HandlerThread handlerThread = new HandlerThread(mTag);
+            handlerThread.start();
+            mLooper = handlerThread.getLooper();
+        }
+        if (mHandler == null) mHandler = new DomainSelectionConnectionHandler(mLooper);
+    }
+
+    /**
+     * Notifies the change of qualified networks.
+     */
+    protected void onQualifiedNetworksChanged() {
+        if (mIsEmergency
+                && (mSelectorType == DomainSelectionService.SELECTOR_TYPE_CALLING)) {
+            // DomainSelectionConnection for emergency calls shall override this.
+            throw new IllegalStateException("DomainSelectionConnection for emergency calls"
+                    + " should override onQualifiedNetworksChanged()");
+        }
+    }
+
+    /**
+     * Dumps local log.
+     */
+    public void dump(@NonNull PrintWriter printWriter) {
+        mLocalLog.dump(printWriter);
+    }
+
+    protected void logd(String msg) {
+        Log.d(mTag, msg);
+    }
+
+    protected void logi(String msg) {
+        Log.i(mTag, msg);
+        mLocalLog.log(msg);
+    }
+
+    protected void loge(String msg) {
+        Log.e(mTag, msg);
+        mLocalLog.log(msg);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/domainselection/DomainSelectionController.java b/src/java/com/android/internal/telephony/domainselection/DomainSelectionController.java
new file mode 100644
index 0000000..52c9960
--- /dev/null
+++ b/src/java/com/android/internal/telephony/domainselection/DomainSelectionController.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2022 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 android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING;
+import static android.telephony.DomainSelectionService.SELECTOR_TYPE_SMS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+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.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}.
+ */
+public class DomainSelectionController {
+    private static final String TAG = "DomainSelectionController";
+    private static final boolean DBG = TelephonyUtils.IS_DEBUGGABLE;
+
+    private static final int EVENT_SERVICE_STATE_CHANGED = 1;
+    private static final int EVENT_BARRING_INFO_CHANGED = 2;
+
+    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);
+
+    protected final Object mLock = new Object();
+    protected final Context mContext;
+
+    protected final int[] mConnectionCounts;
+    private final ArrayList<DomainSelectionConnection> mConnections = new ArrayList<>();
+
+    private final class DomainSelectionControllerHandler extends Handler {
+        DomainSelectionControllerHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            AsyncResult ar;
+            switch (msg.what) {
+                case EVENT_SERVICE_STATE_CHANGED:
+                    ar = (AsyncResult) msg.obj;
+                    updateServiceState((Phone) ar.userObj, (ServiceState) ar.result);
+                    break;
+                case EVENT_BARRING_INFO_CHANGED:
+                    ar = (AsyncResult) msg.obj;
+                    updateBarringInfo((Phone) ar.userObj, (BarringInfo) ar.result);
+                    break;
+                default:
+                    loge("unexpected event=" + msg.what);
+                    break;
+            }
+        }
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * Creates an instance.
+     *
+     * @param context Context object from hosting application.
+     * @param service The {@link DomainSelectionService} instance.
+     * @param looper Handles event messages.
+     */
+    @VisibleForTesting
+    public DomainSelectionController(@NonNull Context context,
+            @NonNull DomainSelectionService service, @Nullable Looper looper) {
+        mContext = context;
+        mDomainSelectionService = service;
+
+        if (looper == null) {
+            mHandlerThread.start();
+            looper = mHandlerThread.getLooper();
+        }
+        mHandler = new DomainSelectionControllerHandler(looper);
+
+        int numPhones = TelephonyManager.getDefault().getActiveModemCount();
+        mConnectionCounts = new int[numPhones];
+        for (int i = 0; i < numPhones; i++) {
+            mConnectionCounts[i] = 0;
+        }
+    }
+
+    /**
+     * Returns a {@link DomainSelectionConnection} instance.
+     *
+     * @param phone Indicates who requests the service.
+     * @param selectorType Indicates the selector type requested.
+     * @param isEmergency Indicates whether this is for emergency service.
+     * @return A {@link DomainSelectiionConnection} instance for the requested service.
+     *         Returns {@code null} if the requested service is not supported.
+     */
+    public @Nullable DomainSelectionConnection getDomainSelectionConnection(
+            @NonNull Phone phone,
+            @DomainSelectionService.SelectorType int selectorType,
+            boolean isEmergency) {
+        DomainSelectionConnection c = null;
+
+        if (selectorType == SELECTOR_TYPE_CALLING) {
+            if (isEmergency) {
+                c = new EmergencyCallDomainSelectionConnection(phone, this);
+            } else {
+                c = new NormalCallDomainSelectionConnection(phone, this);
+            }
+        } else if (selectorType == SELECTOR_TYPE_SMS) {
+            if (isEmergency) {
+                c = new EmergencySmsDomainSelectionConnection(phone, this);
+            } else {
+                c = new SmsDomainSelectionConnection(phone, this);
+            }
+        }
+
+        addConnection(c);
+        return c;
+    }
+
+    private void addConnection(@Nullable DomainSelectionConnection c) {
+        if (c == null) return;
+        mConnections.add(c);
+        registerForStateChange(c);
+    }
+
+    /**
+     * Releases resources for this connection.
+     */
+    public void removeConnection(@Nullable DomainSelectionConnection c) {
+        if (c == null) return;
+        mConnections.remove(c);
+        unregisterForStateChange(c);
+    }
+
+    /**
+     * Requests the domain selection.
+     *
+     * @param attr Attributetes required to determine the domain.
+     * @param callback A callback to receive the response.
+     */
+    public void selectDomain(@NonNull DomainSelectionService.SelectionAttributes attr,
+            @NonNull TransportSelectorCallback callback) {
+        if (attr == null || callback == null) return;
+        if (DBG) logd("selectDomain");
+
+        Executor e = mDomainSelectionService.getCachedExecutor();
+        e.execute(() -> mDomainSelectionService.onDomainSelection(attr, callback));
+    }
+
+    /**
+     * Notifies the change in {@link ServiceState} for a specific slot.
+     *
+     * @param phone {@link Phone} which the state changed.
+     * @param serviceState Updated {@link ServiceState}.
+     */
+    private void updateServiceState(Phone phone, ServiceState serviceState) {
+        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));
+    }
+
+    /**
+     * Notifies the change in {@link BarringInfo} for a specific slot.
+     *
+     * @param phone {@link Phone} which the state changed.
+     * @param info Updated {@link BarringInfo}.
+     */
+    private void updateBarringInfo(Phone phone, BarringInfo info) {
+        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));
+    }
+
+    /**
+     * Registers for the notification of {@link ServiceState} and {@link BarringInfo}.
+     *
+     * @param c {@link DomainSelectionConnection} for which the registration is requested.
+     */
+    private void registerForStateChange(DomainSelectionConnection c) {
+        Phone phone = c.getPhone();
+        int count = mConnectionCounts[phone.getPhoneId()];
+        if (count < 0) count = 0;
+
+        mConnectionCounts[phone.getPhoneId()] = count + 1;
+        if (count > 0) return;
+
+        phone.registerForServiceStateChanged(mHandler, EVENT_SERVICE_STATE_CHANGED, phone);
+        phone.mCi.registerForBarringInfoChanged(mHandler, EVENT_BARRING_INFO_CHANGED, phone);
+
+        updateServiceState(phone, phone.getServiceStateTracker().getServiceState());
+        updateBarringInfo(phone, phone.mCi.getLastBarringInfo());
+    }
+
+    /**
+     * Unregisters for the notification of {@link ServiceState} and {@link BarringInfo}.
+     *
+     * @param c {@link DomainSelectionConnection} for which the unregistration is requested.
+     */
+    private void unregisterForStateChange(DomainSelectionConnection c) {
+        Phone phone = c.getPhone();
+        int count = mConnectionCounts[phone.getPhoneId()];
+        if (count < 1) count = 1;
+
+        mConnectionCounts[phone.getPhoneId()] = count - 1;
+        if (count > 1) return;
+
+        phone.unregisterForServiceStateChanged(mHandler);
+        phone.mCi.unregisterForBarringInfoChanged(mHandler);
+    }
+
+    /**
+     * Notifies the {@link DomainSelectionConnection} instances registered
+     * of the service disconnection.
+     */
+    private void notifyServiceDisconnected() {
+        for (DomainSelectionConnection c : mConnections) {
+            c.onServiceDisconnected();
+        }
+    }
+
+    /**
+     * Gets the {@link Executor} which executes methods of {@link DomainSelectionService.}
+     * @return {@link Executor} instance.
+     */
+    public @NonNull Executor getDomainSelectionServiceExecutor() {
+        return mDomainSelectionService.getCachedExecutor();
+    }
+
+    /**
+     * Dumps logcal log
+     */
+    public void dump(@NonNull PrintWriter printWriter) {
+        mLocalLog.dump(printWriter);
+    }
+
+    private void logd(String msg) {
+        Log.d(TAG, msg);
+    }
+
+    private void logi(String msg) {
+        Log.i(TAG, msg);
+        mLocalLog.log(msg);
+    }
+
+    private void loge(String msg) {
+        Log.e(TAG, msg);
+        mLocalLog.log(msg);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/domainselection/DomainSelectionResolver.java b/src/java/com/android/internal/telephony/domainselection/DomainSelectionResolver.java
new file mode 100644
index 0000000..cbb74fa
--- /dev/null
+++ b/src/java/com/android/internal/telephony/domainselection/DomainSelectionResolver.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2022 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 android.telephony.TelephonyManager.HAL_SERVICE_NETWORK;
+
+import static com.android.internal.telephony.RIL.RADIO_HAL_VERSION_2_1;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.telephony.DomainSelectionService;
+import android.util.IndentingPrintWriter;
+import android.util.LocalLog;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * This class is an entry point to provide whether the AOSP domain selection is supported or not,
+ * and bind the {@link DomainSelectionController} with the given {@link DomainSelectionService} to
+ * provide a specific {@link DomainSelectionConnection} object for communicating with each domain
+ * selector.
+ */
+public class DomainSelectionResolver {
+    private static final String TAG = DomainSelectionResolver.class.getSimpleName();
+    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.
+     */
+    public static void make(Context context, boolean deviceConfigEnabled) {
+        if (sInstance == null) {
+            sInstance = new DomainSelectionResolver(context, deviceConfigEnabled);
+        }
+    }
+
+    /**
+     * Returns the singleton instance of DomainSelectionResolver.
+     *
+     * @return A {@link DomainSelectionResolver} instance.
+     */
+    public static DomainSelectionResolver getInstance() {
+        if (sInstance == null) {
+            throw new IllegalStateException("DomainSelectionResolver is not ready!");
+        }
+        return sInstance;
+    }
+
+    /**
+     * Sets a {@link DomainSelectionResolver} for injecting mock DomainSelectionResolver.
+     *
+     * @param resolver A {@link DomainSelectionResolver} instance to test.
+     */
+    @VisibleForTesting
+    public static void setDomainSelectionResolver(DomainSelectionResolver resolver) {
+        sInstance = resolver;
+    }
+
+    /**
+     * Testing interface for injecting mock DomainSelectionController.
+     */
+    @VisibleForTesting
+    public interface DomainSelectionControllerFactory {
+        /**
+         * Returns a {@link DomainSelectionController} created using the specified
+         * context and {@link DomainSelectionService} instance.
+         */
+        DomainSelectionController create(@NonNull Context context,
+                @NonNull DomainSelectionService service);
+    }
+
+    private DomainSelectionControllerFactory mDomainSelectionControllerFactory =
+            new DomainSelectionControllerFactory() {
+        @Override
+        public DomainSelectionController create(@NonNull Context context,
+                @NonNull DomainSelectionService service) {
+            return new DomainSelectionController(context, service);
+        }
+    };
+
+    // 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;
+    // DomainSelectionController, which are bound to DomainSelectionService.
+    private DomainSelectionController mController;
+
+    public DomainSelectionResolver(Context context, boolean deviceConfigEnabled) {
+        mContext = context;
+        mDeviceConfigEnabled = deviceConfigEnabled;
+        logi("DomainSelectionResolver created: device-config=" + deviceConfigEnabled);
+    }
+
+    /**
+     * Checks if the device supports the domain selection service to route the call / SMS /
+     * supplementary services to the appropriate domain.
+     * This checks the device-config and Radio HAL version for supporting the domain selection.
+     * The domain selection requires the Radio HAL version greater than or equal to 2.1.
+     *
+     * @return {@code true} if the domain selection is supported on the device,
+     *         {@code false} otherwise.
+     */
+    public boolean isDomainSelectionSupported() {
+        return mDeviceConfigEnabled && PhoneFactory.getDefaultPhone()
+                .getHalVersion(HAL_SERVICE_NETWORK).greaterOrEqual(RADIO_HAL_VERSION_2_1);
+    }
+
+    /**
+     * Returns a {@link DomainSelectionConnection} instance.
+     *
+     * @param phone The Phone instance for witch this request is.
+     * @param selectorType Indicates the selector type requested.
+     * @param isEmergency Indicates whether this is for emergency service.
+     * @throws IllegalStateException If the {@link DomainSelectionController} is not created
+     *         because {@link #initialize} method is not called even if the domain selection is
+     *         supported.
+     * @return A {@link DomainSelectionConnection} instance if the device supports
+     *         AOSP domain selection and IMS is available or {@code null} otherwise.
+     */
+    public @Nullable DomainSelectionConnection getDomainSelectionConnection(Phone phone,
+            @DomainSelectionService.SelectorType int selectorType, boolean isEmergency) {
+        if (mController == null) {
+            // If the caller calls this method without checking whether the domain selection
+            // is supported or not, this exception will be thrown.
+            throw new IllegalStateException("DomainSelection is not supported!");
+        }
+
+        if (phone == null || !phone.isImsAvailable()) {
+            // If ImsPhone is null or the binder of ImsService is not available,
+            // CS domain is used for the telephony services.
+            return null;
+        }
+
+        return mController.getDomainSelectionConnection(phone, selectorType, isEmergency);
+    }
+
+    /** Sets a factory interface for creating {@link DomainSelectionController} instance. */
+    @VisibleForTesting
+    public void setDomainSelectionControllerFactory(DomainSelectionControllerFactory factory) {
+        mDomainSelectionControllerFactory = factory;
+    }
+
+    /**
+     * 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.
+     */
+    public void initialize(@NonNull DomainSelectionService service) {
+        logi("Initialize.");
+        mController = mDomainSelectionControllerFactory.create(mContext, service);
+    }
+
+    /**
+     * Dumps this instance into a readable format for dumpsys usage.
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+        ipw.println("Resolver:");
+        ipw.increaseIndent();
+        ipw.println("Event Log:");
+        ipw.increaseIndent();
+        mEventLog.dump(ipw);
+        ipw.decreaseIndent();
+        ipw.decreaseIndent();
+
+        ipw.println("Controller:");
+        ipw.increaseIndent();
+        DomainSelectionController controller = mController;
+        if (controller == null) {
+            ipw.println("no active controller");
+        } else {
+            controller.dump(ipw);
+        }
+        ipw.decreaseIndent();
+    }
+
+    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
new file mode 100644
index 0000000..5f3c3b6
--- /dev/null
+++ b/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2022 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 android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WLAN;
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+import static android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+
+import static com.android.internal.telephony.PhoneConstants.DOMAIN_NON_3GPP_PS;
+import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WLAN;
+import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WWAN;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+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.NetworkRegistrationInfo;
+import android.telephony.data.ApnSetting;
+import android.telephony.ims.ImsReasonInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.data.AccessNetworksManager;
+import com.android.internal.telephony.emergency.EmergencyStateTracker;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Manages the information of request and the callback binder for emergency calling.
+ */
+public class EmergencyCallDomainSelectionConnection extends DomainSelectionConnection {
+
+    private static final boolean DBG = false;
+
+    private @NonNull EmergencyStateTracker mEmergencyStateTracker = null;
+    private @Nullable DomainSelectionConnectionCallback mCallback;
+    private @TransportType int mPreferredTransportType = TRANSPORT_TYPE_INVALID;
+
+    /**
+     * Create an instance.
+     *
+     * @param phone For which this service is requested.
+     * @param controller The controller to communicate with the domain selection service.
+     */
+    public EmergencyCallDomainSelectionConnection(@NonNull Phone phone,
+            @NonNull DomainSelectionController controller) {
+        this(phone, controller, EmergencyStateTracker.getInstance());
+    }
+
+    /**
+     * Create an instance.
+     *
+     * @param phone For which this service is requested.
+     * @param controller The controller to communicate with the domain selection service.
+     * @param tracker The {@link EmergencyStateTracker} instance.
+     */
+    @VisibleForTesting
+    public EmergencyCallDomainSelectionConnection(@NonNull Phone phone,
+            @NonNull DomainSelectionController controller, @NonNull EmergencyStateTracker tracker) {
+        super(phone, SELECTOR_TYPE_CALLING, true, controller);
+        mTag = "EmergencyCallDomainSelectionConnection";
+
+        mEmergencyStateTracker = tracker;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void onWlanSelected(boolean useEmergencyPdn) {
+        mEmergencyStateTracker.onEmergencyTransportChanged(
+                EmergencyStateTracker.EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WLAN);
+        if (useEmergencyPdn) {
+            AccessNetworksManager anm = mPhone.getAccessNetworksManager();
+            int transportType = anm.getPreferredTransport(ApnSetting.TYPE_EMERGENCY);
+            logi("onWlanSelected curTransportType=" + transportType);
+            if (transportType != TRANSPORT_TYPE_WLAN) {
+                changePreferredTransport(TRANSPORT_TYPE_WLAN);
+                return;
+            }
+        }
+
+        CompletableFuture<Integer> future = getCompletableFuture();
+        if (future != null) future.complete(DOMAIN_NON_3GPP_PS);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void onWwanSelected() {
+        mEmergencyStateTracker.onEmergencyTransportChanged(
+                EmergencyStateTracker.EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WWAN);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void onSelectionTerminated(@DisconnectCauses int cause) {
+        if (mCallback != null) mCallback.onSelectionTerminated(cause);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void onDomainSelected(@NetworkRegistrationInfo.Domain int domain,
+            boolean useEmergencyPdn) {
+        if (domain == DOMAIN_PS && useEmergencyPdn) {
+            AccessNetworksManager anm = mPhone.getAccessNetworksManager();
+            int transportType = anm.getPreferredTransport(ApnSetting.TYPE_EMERGENCY);
+            logi("onDomainSelected curTransportType=" + transportType);
+            if (transportType != TRANSPORT_TYPE_WWAN) {
+                changePreferredTransport(TRANSPORT_TYPE_WWAN);
+                return;
+            }
+        }
+        super.onDomainSelected(domain, useEmergencyPdn);
+    }
+
+    /**
+     * Request a domain for emergency call.
+     *
+     * @param attr The attributes required to determine the domain.
+     * @param callback A callback to receive the response.
+     * @return the callback to receive the response.
+     */
+    public @NonNull CompletableFuture<Integer> createEmergencyConnection(
+            @NonNull DomainSelectionService.SelectionAttributes attr,
+            @NonNull DomainSelectionConnectionCallback callback) {
+        mCallback = callback;
+        selectDomain(attr);
+        return getCompletableFuture();
+    }
+
+    private void changePreferredTransport(@TransportType int transportType) {
+        logi("changePreferredTransport " + transportType);
+        initHandler();
+        mPreferredTransportType = transportType;
+        AccessNetworksManager anm = mPhone.getAccessNetworksManager();
+        anm.registerForQualifiedNetworksChanged(mHandler, EVENT_QUALIFIED_NETWORKS_CHANGED);
+        mPhone.notifyEmergencyDomainSelected(transportType);
+    }
+
+    private AccessNetworksManager.AccessNetworksManagerCallback mPreferredTransportCallback =
+            new AccessNetworksManager.AccessNetworksManagerCallback(Runnable::run) {
+        @Override
+        public void onPreferredTransportChanged(@NetCapability int capability) {
+        }
+    };
+
+    /** {@inheritDoc} */
+    @Override
+    protected void onQualifiedNetworksChanged() {
+        AccessNetworksManager anm = mPhone.getAccessNetworksManager();
+        int preferredTransport = anm.getPreferredTransport(ApnSetting.TYPE_EMERGENCY);
+        logi("onQualifiedNetworksChanged preferred=" + mPreferredTransportType
+                + ", current=" + preferredTransport);
+        if (preferredTransport == mPreferredTransportType) {
+            CompletableFuture<Integer> future = getCompletableFuture();
+            if (future != null) {
+                if (preferredTransport == TRANSPORT_TYPE_WLAN) {
+                    future.complete(DOMAIN_NON_3GPP_PS);
+                } else {
+                    future.complete(DOMAIN_PS);
+                }
+            }
+            anm.unregisterForQualifiedNetworksChanged(mHandler);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void cancelSelection() {
+        logi("cancelSelection");
+        AccessNetworksManager anm = mPhone.getAccessNetworksManager();
+        anm.unregisterForQualifiedNetworksChanged(mHandler);
+        super.cancelSelection();
+    }
+
+    /**
+     * Returns the attributes required to determine the domain for a telephony service.
+     *
+     * @param slotId The slot identifier.
+     * @param subId The subscription identifier.
+     * @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 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.
+     * @return The attributes required to determine the domain.
+     */
+    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) {
+        DomainSelectionService.SelectionAttributes.Builder builder =
+                new DomainSelectionService.SelectionAttributes.Builder(
+                        slotId, subId, SELECTOR_TYPE_CALLING)
+                .setEmergency(true)
+                .setExitedFromAirplaneMode(exited)
+                .setCallId(callId)
+                .setNumber(number)
+                .setCsDisconnectCause(callFailCause);
+
+        if (imsReasonInfo != null) builder.setPsDisconnectCause(imsReasonInfo);
+        if (emergencyRegResult != null) builder.setEmergencyRegResult(emergencyRegResult);
+
+        return builder.build();
+    }
+}
diff --git a/src/java/com/android/internal/telephony/domainselection/EmergencySmsDomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/EmergencySmsDomainSelectionConnection.java
new file mode 100644
index 0000000..efcdf11
--- /dev/null
+++ b/src/java/com/android/internal/telephony/domainselection/EmergencySmsDomainSelectionConnection.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2022 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 com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WLAN;
+import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WWAN;
+
+import android.annotation.NonNull;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.AccessNetworkConstants.TransportType;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.data.ApnSetting;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.data.AccessNetworksManager;
+import com.android.internal.telephony.emergency.EmergencyStateTracker;
+
+/**
+ * Manages the information of request and the callback binder for an emergency SMS.
+ */
+public class EmergencySmsDomainSelectionConnection extends SmsDomainSelectionConnection {
+    private final Object mLock = new Object();
+    private @NonNull EmergencyStateTracker mEmergencyStateTracker;
+    private @TransportType int mPreferredTransportType =
+            AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
+
+    public EmergencySmsDomainSelectionConnection(
+            Phone phone, DomainSelectionController controller) {
+        this(phone, controller, EmergencyStateTracker.getInstance());
+    }
+
+    @VisibleForTesting
+    public EmergencySmsDomainSelectionConnection(Phone phone,
+            DomainSelectionController controller, EmergencyStateTracker tracker) {
+        super(phone, controller, true);
+        mTag = "DomainSelectionConnection-EmergencySMS";
+        mEmergencyStateTracker = tracker;
+    }
+
+    /**
+     * Notifies that WLAN transport has been selected.
+     *
+     * @param useEmergencyPdn A flag specifying whether Wi-Fi emergency service uses emergency PDN
+     *                        or not.
+     */
+    @Override
+    public void onWlanSelected(boolean useEmergencyPdn) {
+        synchronized (mLock) {
+            if (mPreferredTransportType != AccessNetworkConstants.TRANSPORT_TYPE_INVALID) {
+                logi("Domain selection completion is in progress");
+                return;
+            }
+
+            mEmergencyStateTracker.onEmergencyTransportChanged(
+                    EmergencyStateTracker.EMERGENCY_TYPE_SMS, MODE_EMERGENCY_WLAN);
+
+            if (useEmergencyPdn) {
+                // Change the transport type if the current preferred transport type for
+                // an emergency is not {@link AccessNetworkConstants#TRANSPORT_TYPE_WLAN}.
+                AccessNetworksManager anm = mPhone.getAccessNetworksManager();
+                if (anm.getPreferredTransport(ApnSetting.TYPE_EMERGENCY)
+                        != AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
+                    changePreferredTransport(AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+                    // The {@link #onDomainSlected()} will be called after the preferred transport
+                    // is successfully changed and notified from the {@link AccessNetworksManager}.
+                    return;
+                }
+            }
+
+            super.onWlanSelected(useEmergencyPdn);
+        }
+    }
+
+    @Override
+    public void onWwanSelected() {
+        mEmergencyStateTracker.onEmergencyTransportChanged(
+                EmergencyStateTracker.EMERGENCY_TYPE_SMS, MODE_EMERGENCY_WWAN);
+    }
+
+    /**
+     * Notifies the domain selected.
+     *
+     * @param domain The selected domain.
+     * @param useEmergencyPdn A flag specifying whether emergency service uses emergency PDN or not.
+     */
+    @Override
+    public void onDomainSelected(@NetworkRegistrationInfo.Domain int domain,
+            boolean useEmergencyPdn) {
+        synchronized (mLock) {
+            if (mPreferredTransportType != AccessNetworkConstants.TRANSPORT_TYPE_INVALID) {
+                logi("Domain selection completion is in progress");
+                return;
+            }
+
+            if (useEmergencyPdn && domain == NetworkRegistrationInfo.DOMAIN_PS) {
+                // Change the transport type if the current preferred transport type for
+                // an emergency is not {@link AccessNetworkConstants#TRANSPORT_TYPE_WWAN}.
+                AccessNetworksManager anm = mPhone.getAccessNetworksManager();
+                if (anm.getPreferredTransport(ApnSetting.TYPE_EMERGENCY)
+                        != AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
+                    changePreferredTransport(AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+                    // The {@link #onDomainSlected()} will be called after the preferred transport
+                    // is successfully changed and notified from the {@link AccessNetworksManager}.
+                    return;
+                }
+            }
+
+            super.onDomainSelected(domain, useEmergencyPdn);
+        }
+    }
+
+    @Override
+    public void finishSelection() {
+        AccessNetworksManager anm = mPhone.getAccessNetworksManager();
+
+        synchronized (mLock) {
+            if (mPreferredTransportType != AccessNetworkConstants.TRANSPORT_TYPE_INVALID) {
+                mPreferredTransportType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
+                anm.unregisterForQualifiedNetworksChanged(mHandler);
+            }
+        }
+
+        super.finishSelection();
+    }
+
+    @Override
+    protected void onQualifiedNetworksChanged() {
+        AccessNetworksManager anm = mPhone.getAccessNetworksManager();
+        int preferredTransportType = anm.getPreferredTransport(ApnSetting.TYPE_EMERGENCY);
+
+        synchronized (mLock) {
+            if (preferredTransportType == mPreferredTransportType) {
+                mPreferredTransportType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
+                super.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS, true);
+                anm.unregisterForQualifiedNetworksChanged(mHandler);
+            }
+        }
+    }
+
+    private void changePreferredTransport(@TransportType int transportType) {
+        logi("Change preferred transport: " + transportType);
+        initHandler();
+        mPreferredTransportType = transportType;
+        AccessNetworksManager anm = mPhone.getAccessNetworksManager();
+        anm.registerForQualifiedNetworksChanged(mHandler, EVENT_QUALIFIED_NETWORKS_CHANGED);
+        mPhone.notifyEmergencyDomainSelected(transportType);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnection.java
new file mode 100644
index 0000000..e157d24
--- /dev/null
+++ b/src/java/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnection.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2022 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 android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
+import android.telephony.Annotation.DisconnectCauses;
+import android.telephony.DomainSelectionService;
+import android.telephony.DomainSelectionService.EmergencyScanType;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.ims.ImsReasonInfo;
+
+import com.android.internal.telephony.Phone;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Manages the information of request and the callback binder for normal calling.
+ */
+public class NormalCallDomainSelectionConnection extends DomainSelectionConnection {
+
+    private static final boolean DBG = false;
+
+    private static final String PREFIX_WPS = "*272";
+
+    // WPS prefix when CLIR is being activated for the call.
+    private static final String PREFIX_WPS_CLIR_ACTIVATE = "*31#*272";
+
+    // WPS prefix when CLIR is being deactivated for the call.
+    private static final String PREFIX_WPS_CLIR_DEACTIVATE = "#31#*272";
+
+
+    private @Nullable DomainSelectionConnectionCallback mCallback;
+
+    /**
+     * Create an instance.
+     *
+     * @param phone For which this service is requested.
+     * @param controller The controller to communicate with the domain selection service.
+     */
+    public NormalCallDomainSelectionConnection(@NonNull Phone phone,
+            @NonNull DomainSelectionController controller) {
+        super(phone, SELECTOR_TYPE_CALLING, false, controller);
+        mTag = "NormalCallDomainSelectionConnection";
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void onWlanSelected() {
+        CompletableFuture<Integer> future = getCompletableFuture();
+        future.complete(NetworkRegistrationInfo.DOMAIN_PS);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void onWwanSelected() {
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void onSelectionTerminated(@DisconnectCauses int cause) {
+        if (mCallback != null) mCallback.onSelectionTerminated(cause);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void onRequestEmergencyNetworkScan(@RadioAccessNetworkType int[] preferredNetworks,
+            @EmergencyScanType int scanType) {
+        // Not expected with normal calling.
+        // Override to prevent abnormal behavior.
+    }
+
+    /**
+     * Request a domain for normal call.
+     *
+     * @param attr The attributes required to determine the domain.
+     * @param callback A callback to receive the response.
+     * @return A {@link CompletableFuture} callback to receive the result.
+     */
+    public CompletableFuture<Integer> createNormalConnection(
+            @NonNull DomainSelectionService.SelectionAttributes attr,
+            @NonNull DomainSelectionConnectionCallback callback) {
+        mCallback = callback;
+        selectDomain(attr);
+        return getCompletableFuture();
+    }
+
+    /**
+     * Returns the attributes required to determine the domain for a normal call.
+     *
+     * @param slotId The slot identifier.
+     * @param subId The subscription identifier.
+     * @param callId The call identifier.
+     * @param number The dialed number.
+     * @param isVideoCall flag for video call.
+     * @param callFailCause The reason why the last CS attempt failed.
+     * @param imsReasonInfo The reason why the last PS attempt failed.
+     * @return The attributes required to determine the domain.
+     */
+    public static @NonNull DomainSelectionService.SelectionAttributes getSelectionAttributes(
+            int slotId, int subId, @NonNull String callId, @NonNull String number,
+            boolean isVideoCall, int callFailCause, @Nullable ImsReasonInfo imsReasonInfo) {
+
+        DomainSelectionService.SelectionAttributes.Builder builder =
+                new DomainSelectionService.SelectionAttributes.Builder(
+                        slotId, subId, SELECTOR_TYPE_CALLING)
+                        .setEmergency(false)
+                        .setCallId(callId)
+                        .setNumber(number)
+                        .setCsDisconnectCause(callFailCause)
+                        .setVideoCall(isVideoCall);
+
+        if (imsReasonInfo != null) {
+            builder.setPsDisconnectCause(imsReasonInfo);
+        }
+        return builder.build();
+    }
+
+    /**
+     * Check if the call is Wireless Priority Service call
+     * @param dialString  The number being dialed.
+     * @return {@code true} if dialString matches WPS pattern and {@code false} otherwise.
+     */
+    public static boolean isWpsCall(String dialString) {
+        return (dialString != null) && (dialString.startsWith(PREFIX_WPS)
+                || dialString.startsWith(PREFIX_WPS_CLIR_ACTIVATE)
+                || dialString.startsWith(PREFIX_WPS_CLIR_DEACTIVATE));
+    }
+}
diff --git a/src/java/com/android/internal/telephony/domainselection/SmsDomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/SmsDomainSelectionConnection.java
new file mode 100644
index 0000000..36a7b17
--- /dev/null
+++ b/src/java/com/android/internal/telephony/domainselection/SmsDomainSelectionConnection.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2022 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 android.telephony.DomainSelectionService.SELECTOR_TYPE_SMS;
+
+import android.annotation.NonNull;
+import android.telephony.Annotation.DisconnectCauses;
+import android.telephony.DomainSelectionService;
+import android.telephony.NetworkRegistrationInfo;
+
+import com.android.internal.telephony.Phone;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Manages the information of request and the callback binder for SMS.
+ */
+public class SmsDomainSelectionConnection extends DomainSelectionConnection {
+    private DomainSelectionConnectionCallback mCallback;
+
+    public SmsDomainSelectionConnection(Phone phone, DomainSelectionController controller) {
+        this(phone, controller, false);
+        mTag = "DomainSelectionConnection-SMS";
+    }
+
+    protected SmsDomainSelectionConnection(Phone phone, DomainSelectionController controller,
+            boolean isEmergency) {
+        super(phone, SELECTOR_TYPE_SMS, isEmergency, controller);
+    }
+
+    @Override
+    public void onWlanSelected() {
+        super.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS);
+    }
+
+    @Override
+    public void onSelectionTerminated(@DisconnectCauses int cause) {
+        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.
+     *
+     * @param attr The attributes required to determine the domain.
+     * @param callback A callback to notify an error of the domain selection.
+     * @return A {@link CompletableFuture} to get the selected domain
+     *         {@link NetworkRegistrationInfo#DOMAIN_PS} or
+     *         {@link NetworkRegistrationInfo#DOMAIN_CS}.
+     */
+    public @NonNull CompletableFuture<Integer> requestDomainSelection(
+            @NonNull DomainSelectionService.SelectionAttributes attr,
+            @NonNull DomainSelectionConnectionCallback callback) {
+        mCallback = callback;
+        selectDomain(attr);
+        return getCompletableFuture();
+    }
+}
diff --git a/src/java/com/android/internal/telephony/emergency/EmergencyConstants.java b/src/java/com/android/internal/telephony/emergency/EmergencyConstants.java
new file mode 100644
index 0000000..6caf5ab
--- /dev/null
+++ b/src/java/com/android/internal/telephony/emergency/EmergencyConstants.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 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.emergency;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Define the constants for emergency call domain selection.
+ */
+public class EmergencyConstants {
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"MODE_EMERGENCY_"},
+            value = {
+                    MODE_EMERGENCY_NONE,
+                    MODE_EMERGENCY_WWAN,
+                    MODE_EMERGENCY_WLAN,
+                    MODE_EMERGENCY_CALLBACK,
+            })
+    public @interface EmergencyMode {}
+
+    /**
+     * Default value.
+     */
+    public static final int MODE_EMERGENCY_NONE = 0;
+    /**
+     * Mode Type Emergency WWAN, indicates that the current domain selected for the Emergency call
+     * is cellular.
+     */
+    public static final int MODE_EMERGENCY_WWAN = 1;
+    /**
+     * Mode Type Emergency WLAN, indicates that the current domain selected for the Emergency call
+     * is WLAN/WIFI.
+     */
+    public static final int MODE_EMERGENCY_WLAN = 2;
+    /**
+     * Mode Type Emergency Callback, indicates that the current mode set request is for Emergency
+     * callback.
+     */
+    public static final int MODE_EMERGENCY_CALLBACK = 3;
+
+    /** Converts the {@link EmergencyMode} to String */
+    public static String emergencyModeToString(int emcMode) {
+        switch (emcMode) {
+            case MODE_EMERGENCY_NONE: return "NONE";
+            case MODE_EMERGENCY_WWAN: return "WWAN";
+            case MODE_EMERGENCY_WLAN: return "WLAN";
+            case MODE_EMERGENCY_CALLBACK: return "CALLBACK";
+            default: return "UNKNOWN(" + emcMode + ")";
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java b/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
index 8bc1dfa..9b44001 100644
--- a/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
+++ b/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
@@ -19,14 +19,16 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
 import android.os.AsyncResult;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
-import android.os.SystemProperties;
 import android.telephony.CarrierConfigManager;
+import android.telephony.CellIdentity;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
@@ -35,18 +37,20 @@
 import android.telephony.emergency.EmergencyNumber.EmergencyCallRouting;
 import android.telephony.emergency.EmergencyNumber.EmergencyServiceCategories;
 import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.LocalLog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.CommandsInterface;
-import com.android.internal.telephony.HalVersion;
 import com.android.internal.telephony.LocaleTracker;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.ServiceStateTracker;
-import com.android.internal.telephony.SubscriptionController;
+import com.android.internal.telephony.metrics.EmergencyNumberStats;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
+import com.android.internal.telephony.nano.PersistAtomsProto;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.phone.ecc.nano.ProtobufEccData;
@@ -67,6 +71,9 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
 import java.util.zip.GZIPInputStream;
 
 /**
@@ -95,9 +102,18 @@
 
     private final CommandsInterface mCi;
     private final Phone mPhone;
+    private int mPhoneId;
     private String mCountryIso;
     private String mLastKnownEmergencyCountryIso = "";
     private int mCurrentDatabaseVersion = INVALID_DATABASE_VERSION;
+    private int mCurrentOtaDatabaseVersion = INVALID_DATABASE_VERSION;
+    private Resources mResources = null;
+    /**
+     * Used for storing all specific mnc's along with the list of emergency numbers
+     * for which normal routing should be supported.
+     */
+    private Map<String, Set<String>> mNormalRoutedNumbers = new ArrayMap<>();
+
     /**
      * Indicates if the country iso is set by another subscription.
      * @hide
@@ -160,8 +176,10 @@
     public EmergencyNumberTracker(Phone phone, CommandsInterface ci) {
         mPhone = phone;
         mCi = ci;
+        mResources = mPhone.getContext().getResources();
 
         if (mPhone != null) {
+            mPhoneId = phone.getPhoneId();
             CarrierConfigManager configMgr = (CarrierConfigManager)
                     mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
             if (configMgr != null) {
@@ -178,6 +196,12 @@
                 configMgr.registerCarrierConfigChangeListener(this::post,
                         (slotIndex, subId, carrierId, specificCarrierId) ->
                                 onCarrierConfigUpdated(slotIndex));
+
+                //register country change listener
+                IntentFilter filter = new IntentFilter(
+                    TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED);
+                mPhone.getContext().registerReceiver(mIntentReceiver, filter);
+
             } else {
                 loge("CarrierConfigManager is null.");
             }
@@ -265,12 +289,7 @@
     @VisibleForTesting
     public boolean isSimAbsent() {
         for (Phone phone: PhoneFactory.getPhones()) {
-            int slotId;
-            if (phone.isSubscriptionManagerServiceEnabled()) {
-                slotId = SubscriptionManagerService.getInstance().getSlotIndex(phone.getSubId());
-            } else {
-                slotId = SubscriptionController.getInstance().getSlotIndex(phone.getSubId());
-            }
+            int slotId = SubscriptionManagerService.getInstance().getSlotIndex(phone.getSubId());
             // If slot id is invalid, it means that there is no sim card.
             if (slotId != SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
                 // If there is at least one sim active, sim is not absent; it returns false
@@ -285,7 +304,7 @@
         // If country iso has been cached when listener is set, don't need to cache the initial
         // country iso and initial database.
         if (mCountryIso == null) {
-            String countryForDatabaseCache = getInitialCountryIso().toLowerCase();
+            String countryForDatabaseCache = getInitialCountryIso().toLowerCase(Locale.ROOT);
             updateEmergencyCountryIso(countryForDatabaseCache);
             // Use the last known country to cache the database in APM
             if (TextUtils.isEmpty(countryForDatabaseCache)
@@ -408,7 +427,8 @@
                 EVENT_OVERRIDE_OTA_EMERGENCY_NUMBER_DB_FILE_PATH, null).sendToTarget();
     }
 
-    private EmergencyNumber convertEmergencyNumberFromEccInfo(EccInfo eccInfo, String countryIso) {
+    private EmergencyNumber convertEmergencyNumberFromEccInfo(EccInfo eccInfo, String countryIso,
+            int emergencyCallRouting) {
         String phoneNumber = eccInfo.phoneNumber.trim();
         if (phoneNumber.isEmpty()) {
             loge("EccInfo has empty phone number.");
@@ -449,13 +469,65 @@
                     // Ignores unknown types.
             }
         }
-        return new EmergencyNumber(phoneNumber, countryIso, "", emergencyServiceCategoryBitmask,
-                new ArrayList<String>(), EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
-                EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+        return new EmergencyNumber(phoneNumber, countryIso, "",
+                emergencyServiceCategoryBitmask, new ArrayList<String>(),
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE, emergencyCallRouting);
+    }
+
+    /**
+     * Get routing type of emergency numbers from DB. Update mnc's list with numbers that are
+     * to supported as normal routing type in the respective mnc's.
+     */
+    private int getRoutingInfoFromDB(EccInfo eccInfo,
+            Map<String, Set<String>> normalRoutedNumbers) {
+        int emergencyCallRouting;
+        switch(eccInfo.routing)
+        {
+            case EccInfo.Routing.NORMAL :
+                emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL;
+                break;
+            case EccInfo.Routing.EMERGENCY :
+                emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY;
+                break;
+            default:
+                emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN;
+        }
+        String phoneNumber = eccInfo.phoneNumber.trim();
+        if (phoneNumber.isEmpty()) {
+            loge("EccInfo has empty phone number.");
+            return emergencyCallRouting;
+        }
+
+        if (eccInfo.routing == EccInfo.Routing.NORMAL) {
+            emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL;
+
+            if (((eccInfo.normalRoutingMncs).length != 0)
+                    && (eccInfo.normalRoutingMncs[0].length() > 0)) {
+                emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN;
+
+                for (String routingMnc : eccInfo.normalRoutingMncs) {
+                    boolean mncExist = normalRoutedNumbers.containsKey(routingMnc);
+                    Set phoneNumberList;
+                    if (!mncExist) {
+                        phoneNumberList = new ArraySet<String>();
+                        phoneNumberList.add(phoneNumber);
+                        normalRoutedNumbers.put(routingMnc, phoneNumberList);
+                    } else {
+                        phoneNumberList = normalRoutedNumbers.get(routingMnc);
+                        if (!phoneNumberList.contains(phoneNumber)) {
+                            phoneNumberList.add(phoneNumber);
+                        }
+                    }
+                }
+                logd("Normal routed mncs with phoneNumbers:" + normalRoutedNumbers);
+            }
+        }
+        return emergencyCallRouting;
     }
 
     private void cacheEmergencyDatabaseByCountry(String countryIso) {
         int assetsDatabaseVersion;
+        Map<String, Set<String>> assetNormalRoutedNumbers = new ArrayMap<>();
 
         // Read the Asset emergency number database
         List<EmergencyNumber> updatedAssetEmergencyNumberList = new ArrayList<>();
@@ -467,12 +539,17 @@
                     readInputStreamToByteArray(gzipInputStream));
             assetsDatabaseVersion = allEccMessages.revision;
             logd(countryIso + " asset emergency database is loaded. Ver: " + assetsDatabaseVersion
-                    + " Phone Id: " + mPhone.getPhoneId());
+                    + " Phone Id: " + mPhone.getPhoneId() + " countryIso: " + countryIso);
             for (ProtobufEccData.CountryInfo countryEccInfo : allEccMessages.countries) {
-                if (countryEccInfo.isoCode.equals(countryIso.toUpperCase())) {
+                if (countryEccInfo.isoCode.equals(countryIso.toUpperCase(Locale.ROOT))) {
                     for (ProtobufEccData.EccInfo eccInfo : countryEccInfo.eccs) {
+                        int emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN;
+                        if (!shouldEmergencyNumberRoutingFromDbBeIgnored()) {
+                            emergencyCallRouting = getRoutingInfoFromDB(eccInfo,
+                                    assetNormalRoutedNumbers);
+                        }
                         updatedAssetEmergencyNumberList.add(convertEmergencyNumberFromEccInfo(
-                                eccInfo, countryIso));
+                                eccInfo, countryIso, emergencyCallRouting));
                     }
                 }
             }
@@ -483,24 +560,27 @@
         }
 
         // Cache OTA emergency number database
-        int otaDatabaseVersion = cacheOtaEmergencyNumberDatabase();
+        mCurrentOtaDatabaseVersion = cacheOtaEmergencyNumberDatabase();
 
         // Use a valid database that has higher version.
-        if (otaDatabaseVersion == INVALID_DATABASE_VERSION
+        if (mCurrentOtaDatabaseVersion == INVALID_DATABASE_VERSION
                 && assetsDatabaseVersion == INVALID_DATABASE_VERSION) {
             loge("No database available. Phone Id: " + mPhone.getPhoneId());
-        } else if (assetsDatabaseVersion > otaDatabaseVersion) {
+        } else if (assetsDatabaseVersion > mCurrentOtaDatabaseVersion) {
             logd("Using Asset Emergency database. Version: " + assetsDatabaseVersion);
             mCurrentDatabaseVersion = assetsDatabaseVersion;
             mEmergencyNumberListFromDatabase = updatedAssetEmergencyNumberList;
+            mNormalRoutedNumbers.clear();
+            mNormalRoutedNumbers = assetNormalRoutedNumbers;
         } else {
-            logd("Using Ota Emergency database. Version: " + otaDatabaseVersion);
+            logd("Using Ota Emergency database. Version: " + mCurrentOtaDatabaseVersion);
         }
     }
 
     private int cacheOtaEmergencyNumberDatabase() {
         ProtobufEccData.AllInfo allEccMessages = null;
         int otaDatabaseVersion = INVALID_DATABASE_VERSION;
+        Map<String, Set<String>> otaNormalRoutedNumbers = new ArrayMap<>();
 
         // Read the OTA emergency number database
         List<EmergencyNumber> updatedOtaEmergencyNumberList = new ArrayList<>();
@@ -529,10 +609,15 @@
             logd(countryIso + " ota emergency database is loaded. Ver: " + otaDatabaseVersion);
             otaDatabaseVersion = allEccMessages.revision;
             for (ProtobufEccData.CountryInfo countryEccInfo : allEccMessages.countries) {
-                if (countryEccInfo.isoCode.equals(countryIso.toUpperCase())) {
+                if (countryEccInfo.isoCode.equals(countryIso.toUpperCase(Locale.ROOT))) {
                     for (ProtobufEccData.EccInfo eccInfo : countryEccInfo.eccs) {
+                        int emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN;
+                        if (!shouldEmergencyNumberRoutingFromDbBeIgnored()) {
+                            emergencyCallRouting = getRoutingInfoFromDB(eccInfo,
+                                    otaNormalRoutedNumbers);
+                        }
                         updatedOtaEmergencyNumberList.add(convertEmergencyNumberFromEccInfo(
-                                eccInfo, countryIso));
+                                eccInfo, countryIso, emergencyCallRouting));
                     }
                 }
             }
@@ -547,6 +632,8 @@
                 && mCurrentDatabaseVersion < otaDatabaseVersion) {
             mCurrentDatabaseVersion = otaDatabaseVersion;
             mEmergencyNumberListFromDatabase = updatedOtaEmergencyNumberList;
+            mNormalRoutedNumbers.clear();
+            mNormalRoutedNumbers = otaNormalRoutedNumbers;
         }
         return otaDatabaseVersion;
     }
@@ -595,9 +682,9 @@
     private void updateEmergencyNumberListDatabaseAndNotify(String countryIso) {
         logd("updateEmergencyNumberListDatabaseAndNotify(): receiving countryIso: "
                 + countryIso);
-        updateEmergencyCountryIso(countryIso.toLowerCase());
+        updateEmergencyCountryIso(countryIso.toLowerCase(Locale.ROOT));
         // Use cached country iso in APM to load emergency number database.
-        if (TextUtils.isEmpty(countryIso) && isAirplaneModeEnabled()) {
+        if (TextUtils.isEmpty(countryIso)) {
             countryIso = getCountryIsoForCachingDatabase();
             logd("updateEmergencyNumberListDatabaseAndNotify(): using cached APM country "
                     + countryIso);
@@ -626,7 +713,8 @@
     private void updateOtaEmergencyNumberListDatabaseAndNotify() {
         logd("updateOtaEmergencyNumberListDatabaseAndNotify():"
                 + " receiving Emegency Number database OTA update");
-        if (cacheOtaEmergencyNumberDatabase() != INVALID_DATABASE_VERSION) {
+        mCurrentOtaDatabaseVersion = cacheOtaEmergencyNumberDatabase();
+        if (mCurrentOtaDatabaseVersion != INVALID_DATABASE_VERSION) {
             writeUpdatedEmergencyNumberListMetrics(mEmergencyNumberListFromDatabase);
             if (!DBG) {
                 mEmergencyNumberListDatabaseLocalLog.log(
@@ -691,7 +779,11 @@
         }
         mergedEmergencyNumberList.addAll(mEmergencyNumberListWithPrefix);
         mergedEmergencyNumberList.addAll(mEmergencyNumberListFromTestMode);
-        EmergencyNumber.mergeSameNumbersInEmergencyNumberList(mergedEmergencyNumberList);
+        if (shouldDeterminingOfUrnsAndCategoriesWhileMergingIgnored()) {
+            EmergencyNumber.mergeSameNumbersInEmergencyNumberList(mergedEmergencyNumberList);
+        } else {
+            EmergencyNumber.mergeSameNumbersInEmergencyNumberList(mergedEmergencyNumberList, true);
+        }
         mEmergencyNumberList = mergedEmergencyNumberList;
     }
 
@@ -702,11 +794,90 @@
      *         indication not support from the HAL.
      */
     public List<EmergencyNumber> getEmergencyNumberList() {
+        List<EmergencyNumber> completeEmergencyNumberList;
         if (!mEmergencyNumberListFromRadio.isEmpty()) {
-            return Collections.unmodifiableList(mEmergencyNumberList);
+            completeEmergencyNumberList = Collections.unmodifiableList(mEmergencyNumberList);
         } else {
-            return getEmergencyNumberListFromEccListDatabaseAndTest();
+            completeEmergencyNumberList = getEmergencyNumberListFromEccListDatabaseAndTest();
         }
+        if (shouldAdjustForRouting()) {
+            return adjustRoutingForEmergencyNumbers(completeEmergencyNumberList);
+        } else {
+            return completeEmergencyNumberList;
+        }
+    }
+
+    /**
+     * Util function to check whether routing type and mnc value in emergency number needs
+     * to be adjusted for the current network mnc.
+     */
+    private boolean shouldAdjustForRouting() {
+        if (!shouldEmergencyNumberRoutingFromDbBeIgnored() && !mNormalRoutedNumbers.isEmpty()) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Adjust emergency numbers with mnc and routing type based on the current network mnc.
+     */
+    private List<EmergencyNumber> adjustRoutingForEmergencyNumbers(
+            List<EmergencyNumber> emergencyNumbers) {
+        CellIdentity cellIdentity = mPhone.getCurrentCellIdentity();
+        if (cellIdentity != null) {
+            String networkMnc = cellIdentity.getMncString();
+            Set<String> normalRoutedPhoneNumbers = mNormalRoutedNumbers.get(networkMnc);
+            Set<String> normalRoutedPhoneNumbersWithPrefix = new ArraySet<String>();
+
+            if (normalRoutedPhoneNumbers != null && !normalRoutedPhoneNumbers.isEmpty()) {
+                for (String num : normalRoutedPhoneNumbers) {
+                    Set<String> phoneNumbersWithPrefix = addPrefixToEmergencyNumber(num);
+                    if (phoneNumbersWithPrefix != null && !phoneNumbersWithPrefix.isEmpty()) {
+                        normalRoutedPhoneNumbersWithPrefix.addAll(phoneNumbersWithPrefix);
+                    }
+                }
+            }
+            List<EmergencyNumber> adjustedEmergencyNumberList = new ArrayList<>();
+            int routing;
+            String mnc;
+            for (EmergencyNumber num : emergencyNumbers) {
+                routing = num.getEmergencyCallRouting();
+                mnc = num.getMnc();
+                if (num.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE)) {
+                    if ((normalRoutedPhoneNumbers != null
+                            && normalRoutedPhoneNumbers.contains(num.getNumber()))
+                            || normalRoutedPhoneNumbersWithPrefix.contains(num.getNumber())) {
+                        routing = EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL;
+                        mnc = networkMnc;
+                        logd("adjustRoutingForEmergencyNumbers for number" + num.getNumber());
+                    } else if (routing == EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN) {
+                        routing = EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY;
+                    }
+                }
+                adjustedEmergencyNumberList.add(new EmergencyNumber(num.getNumber(),
+                        num.getCountryIso(), mnc,
+                        num.getEmergencyServiceCategoryBitmask(),
+                        num.getEmergencyUrns(), num.getEmergencyNumberSourceBitmask(),
+                        routing));
+            }
+            return adjustedEmergencyNumberList;
+        } else {
+            return emergencyNumbers;
+        }
+    }
+
+
+    /**
+     * Util function to add prefix to the given emergency number.
+     */
+    private Set<String> addPrefixToEmergencyNumber(String number) {
+        Set<String> phoneNumbersWithPrefix = new ArraySet<String>();
+        for (String prefix : mEmergencyNumberPrefix) {
+            if (!number.startsWith(prefix)) {
+                phoneNumbersWithPrefix.add(prefix + number);
+            }
+        }
+        return phoneNumbersWithPrefix;
     }
 
     /**
@@ -714,7 +885,7 @@
      *
      * @return {@code true} if it is; {@code false} otherwise.
      */
-    public boolean isEmergencyNumber(String number, boolean exactMatch) {
+    public boolean isEmergencyNumber(String number) {
         if (number == null) {
             return false;
         }
@@ -730,31 +901,14 @@
 
         if (!mEmergencyNumberListFromRadio.isEmpty()) {
             for (EmergencyNumber num : mEmergencyNumberList) {
-                // According to com.android.i18n.phonenumbers.ShortNumberInfo, in
-                // these countries, if extra digits are added to an emergency number,
-                // it no longer connects to the emergency service.
-                String countryIso = getLastKnownEmergencyCountryIso();
-                if (countryIso.equals("br") || countryIso.equals("cl")
-                        || countryIso.equals("ni")) {
-                    exactMatch = true;
-                } else {
-                    exactMatch = false || exactMatch;
-                }
-                if (exactMatch) {
-                    if (num.getNumber().equals(number)) {
-                        logd("Found in mEmergencyNumberList [exact match] ");
-                        return true;
-                    }
-                } else {
-                    if (number.startsWith(num.getNumber())) {
-                        logd("Found in mEmergencyNumberList [not exact match] ");
-                        return true;
-                    }
+                if (num.getNumber().equals(number)) {
+                    logd("Found in mEmergencyNumberList");
+                    return true;
                 }
             }
             return false;
         } else {
-            boolean inEccList = isEmergencyNumberFromEccList(number, exactMatch);
+            boolean inEccList = isEmergencyNumberFromEccList(number);
             boolean inEmergencyNumberDb = isEmergencyNumberFromDatabase(number);
             boolean inEmergencyNumberTestList = isEmergencyNumberForTest(number);
             logd("Search results - inRilEccList:" + inEccList
@@ -844,6 +998,10 @@
         return mCurrentDatabaseVersion;
     }
 
+    public int getEmergencyNumberOtaDbVersion() {
+        return mCurrentOtaDatabaseVersion;
+    }
+
     private synchronized void updateEmergencyCountryIso(String countryIso) {
         mCountryIso = countryIso;
         if (!TextUtils.isEmpty(mCountryIso)) {
@@ -859,22 +1017,8 @@
      */
     private List<EmergencyNumber> getEmergencyNumberListFromEccList() {
         List<EmergencyNumber> emergencyNumberList = new ArrayList<>();
-        int slotId = SubscriptionController.getInstance().getSlotIndex(mPhone.getSubId());
 
-        String ecclist = (slotId <= 0) ? "ril.ecclist" : ("ril.ecclist" + slotId);
-        String emergencyNumbers = SystemProperties.get(ecclist, "");
-        if (TextUtils.isEmpty(emergencyNumbers)) {
-            // then read-only ecclist property since old RIL only uses this
-            emergencyNumbers = SystemProperties.get("ro.ril.ecclist");
-        }
-        if (!TextUtils.isEmpty(emergencyNumbers)) {
-            // searches through the comma-separated list for a match,
-            // return true if one is found.
-            for (String emergencyNum : emergencyNumbers.split(",")) {
-                emergencyNumberList.add(getLabeledEmergencyNumberForEcclist(emergencyNum));
-            }
-        }
-        emergencyNumbers = ((isSimAbsent()) ? "112,911,000,08,110,118,119,999" : "112,911");
+        String emergencyNumbers = ((isSimAbsent()) ? "112,911,000,08,110,118,119,999" : "112,911");
         for (String emergencyNum : emergencyNumbers.split(",")) {
             emergencyNumberList.add(getLabeledEmergencyNumberForEcclist(emergencyNum));
         }
@@ -890,15 +1034,14 @@
         List<EmergencyNumber> emergencyNumberListWithPrefix = new ArrayList<>();
         if (emergencyNumberList != null) {
             for (EmergencyNumber num : emergencyNumberList) {
-                for (String prefix : mEmergencyNumberPrefix) {
-                    // If an emergency number has started with the prefix,
-                    // no need to apply the prefix.
-                    if (!num.getNumber().startsWith(prefix)) {
+                Set<String> phoneNumbersWithPrefix = addPrefixToEmergencyNumber(num.getNumber());
+                if (phoneNumbersWithPrefix != null && !phoneNumbersWithPrefix.isEmpty()) {
+                    for (String numberWithPrefix : phoneNumbersWithPrefix) {
                         emergencyNumberListWithPrefix.add(new EmergencyNumber(
-                            prefix + num.getNumber(), num.getCountryIso(),
-                            num.getMnc(), num.getEmergencyServiceCategoryBitmask(),
-                            num.getEmergencyUrns(), num.getEmergencyNumberSourceBitmask(),
-                            num.getEmergencyCallRouting()));
+                                numberWithPrefix, num.getCountryIso(),
+                                num.getMnc(), num.getEmergencyServiceCategoryBitmask(),
+                                num.getEmergencyUrns(), num.getEmergencyNumberSourceBitmask(),
+                                num.getEmergencyCallRouting()));
                     }
                 }
             }
@@ -917,7 +1060,7 @@
     }
 
     private boolean isEmergencyNumberFromDatabase(String number) {
-        if (!mPhone.getHalVersion().greaterOrEqual(new HalVersion(1, 4))) {
+        if (mEmergencyNumberListFromDatabase.isEmpty()) {
             return false;
         }
         number = PhoneNumberUtils.stripSeparators(number);
@@ -940,10 +1083,10 @@
         number = PhoneNumberUtils.stripSeparators(number);
         for (EmergencyNumber num : mEmergencyNumberListFromDatabase) {
             if (num.getNumber().equals(number)) {
-                return new EmergencyNumber(number, getLastKnownEmergencyCountryIso().toLowerCase(),
-                        "", num.getEmergencyServiceCategoryBitmask(),
+                return new EmergencyNumber(number, getLastKnownEmergencyCountryIso()
+                        .toLowerCase(Locale.ROOT), "", num.getEmergencyServiceCategoryBitmask(),
                         new ArrayList<String>(), EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
-                        EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+                        num.getEmergencyCallRouting());
             }
         }
         return new EmergencyNumber(number, "", "",
@@ -956,7 +1099,7 @@
      * Back-up old logics for {@link PhoneNumberUtils#isEmergencyNumberInternal} for legacy
      * and deprecate purpose.
      */
-    private boolean isEmergencyNumberFromEccList(String number, boolean useExactMatch) {
+    private boolean isEmergencyNumberFromEccList(String number) {
         // If the number passed in is null, just return false:
         if (number == null) return false;
 
@@ -980,59 +1123,8 @@
         /// @}
 
         String emergencyNumbers = "";
-        int slotId = SubscriptionController.getInstance().getSlotIndex(mPhone.getSubId());
-
-        String ecclist = null;
         String countryIso = getLastKnownEmergencyCountryIso();
-
-        if (!mPhone.getHalVersion().greaterOrEqual(new HalVersion(1, 4))) {
-            //only use ril ecc list for older devices with HAL < 1.4
-            // check read-write ecclist property first
-            ecclist = (slotId <= 0) ? "ril.ecclist" : ("ril.ecclist" + slotId);
-            emergencyNumbers = SystemProperties.get(ecclist, "");
-
-            logd("slotId:" + slotId + " country:" + countryIso + " emergencyNumbers: "
-                + emergencyNumbers);
-
-            if (TextUtils.isEmpty(emergencyNumbers)) {
-                // then read-only ecclist property since old RIL only uses this
-                emergencyNumbers = SystemProperties.get("ro.ril.ecclist");
-            }
-
-            if (!TextUtils.isEmpty(emergencyNumbers)) {
-                // searches through the comma-separated list for a match,
-                // return true if one is found.
-                for (String emergencyNum : emergencyNumbers.split(",")) {
-                    // According to com.android.i18n.phonenumbers.ShortNumberInfo, in
-                    // these countries, if extra digits are added to an emergency number,
-                    // it no longer connects to the emergency service.
-                    if (useExactMatch || countryIso.equals("br") || countryIso.equals("cl")
-                        || countryIso.equals("ni")) {
-                        if (number.equals(emergencyNum)) {
-                            return true;
-                        } else {
-                            for (String prefix : mEmergencyNumberPrefix) {
-                                if (number.equals(prefix + emergencyNum)) {
-                                    return true;
-                                }
-                            }
-                        }
-                    } else {
-                        if (number.startsWith(emergencyNum)) {
-                            return true;
-                        } else {
-                            for (String prefix : mEmergencyNumberPrefix) {
-                                if (number.startsWith(prefix + emergencyNum)) {
-                                    return true;
-                                }
-                            }
-                        }
-                    }
-                }
-                // no matches found against the list!
-                return false;
-            }
-        }
+        logd("country:" + countryIso);
 
         logd("System property doesn't provide any emergency numbers."
                 + " Use embedded logic for determining ones.");
@@ -1042,57 +1134,32 @@
         emergencyNumbers = ((isSimAbsent()) ? "112,911,000,08,110,118,119,999" : "112,911");
 
         for (String emergencyNum : emergencyNumbers.split(",")) {
-            if (useExactMatch) {
-                if (number.equals(emergencyNum)) {
-                    return true;
-                } else {
-                    for (String prefix : mEmergencyNumberPrefix) {
-                        if (number.equals(prefix + emergencyNum)) {
-                            return true;
-                        }
-                    }
-                }
+            if (number.equals(emergencyNum)) {
+                return true;
             } else {
-                if (number.startsWith(emergencyNum)) {
-                    return true;
-                } else {
-                    for (String prefix : mEmergencyNumberPrefix) {
-                        if (number.equals(prefix + emergencyNum)) {
-                            return true;
-                        }
+                for (String prefix : mEmergencyNumberPrefix) {
+                    if (number.equals(prefix + emergencyNum)) {
+                        return true;
                     }
                 }
             }
         }
 
-        if(isSimAbsent()) {
+        if (isSimAbsent()) {
             // No ecclist system property, so use our own list.
             if (countryIso != null) {
                 ShortNumberInfo info = ShortNumberInfo.getInstance();
-                if (useExactMatch) {
-                    if (info.isEmergencyNumber(number, countryIso.toUpperCase())) {
-                        return true;
-                    } else {
-                        for (String prefix : mEmergencyNumberPrefix) {
-                            if (info.isEmergencyNumber(prefix + number, countryIso.toUpperCase())) {
-                                return true;
-                            }
-                        }
-                    }
-                    return false;
+                if (info.isEmergencyNumber(number, countryIso.toUpperCase(Locale.ROOT))) {
+                    return true;
                 } else {
-                    if (info.connectsToEmergencyNumber(number, countryIso.toUpperCase())) {
-                        return true;
-                    } else {
-                        for (String prefix : mEmergencyNumberPrefix) {
-                            if (info.connectsToEmergencyNumber(prefix + number,
-                                    countryIso.toUpperCase())) {
-                                return true;
-                            }
+                    for (String prefix : mEmergencyNumberPrefix) {
+                        if (info.isEmergencyNumber(prefix + number,
+                                countryIso.toUpperCase(Locale.ROOT))) {
+                            return true;
                         }
                     }
-                    return false;
                 }
+                return false;
             }
         }
 
@@ -1111,7 +1178,7 @@
      */
     private void updateEmergencyNumberListTestModeAndNotify(int action, EmergencyNumber num) {
         if (action == ADD_EMERGENCY_NUMBER_TEST_MODE) {
-            if (!isEmergencyNumber(num.getNumber(), true)) {
+            if (!isEmergencyNumber(num.getNumber())) {
                 mEmergencyNumberListFromTestMode.add(num);
             }
         } else if (action == RESET_EMERGENCY_NUMBER_TEST_MODE) {
@@ -1138,7 +1205,7 @@
 
     private List<EmergencyNumber> getEmergencyNumberListFromEccListDatabaseAndTest() {
         List<EmergencyNumber> mergedEmergencyNumberList = getEmergencyNumberListFromEccList();
-        if (mPhone.getHalVersion().greaterOrEqual(new HalVersion(1, 4))) {
+        if (!mEmergencyNumberListFromDatabase.isEmpty()) {
             loge("getEmergencyNumberListFromEccListDatabaseAndTest: radio indication is"
                     + " unavailable in 1.4 HAL.");
             mergedEmergencyNumberList.addAll(mEmergencyNumberListFromDatabase);
@@ -1146,7 +1213,12 @@
                     mEmergencyNumberListFromDatabase));
         }
         mergedEmergencyNumberList.addAll(getEmergencyNumberListTestMode());
-        EmergencyNumber.mergeSameNumbersInEmergencyNumberList(mergedEmergencyNumberList);
+
+        if (shouldDeterminingOfUrnsAndCategoriesWhileMergingIgnored()) {
+            EmergencyNumber.mergeSameNumbersInEmergencyNumberList(mergedEmergencyNumberList);
+        } else {
+            EmergencyNumber.mergeSameNumbersInEmergencyNumberList(mergedEmergencyNumberList, true);
+        }
         return mergedEmergencyNumberList;
     }
 
@@ -1162,16 +1234,16 @@
         return new ArrayList<>(mEmergencyNumberListFromRadio);
     }
 
-    private static void logd(String str) {
-        Rlog.d(TAG, str);
+    private void logd(String str) {
+        Rlog.d(TAG, "[" + mPhoneId + "]" + str);
     }
 
-    private static void logw(String str) {
-        Rlog.w(TAG, str);
+    private void logw(String str) {
+        Rlog.w(TAG, "[" + mPhoneId + "]" + str);
     }
 
-    private static void loge(String str) {
-        Rlog.e(TAG, str);
+    private void loge(String str) {
+        Rlog.e(TAG, "[" + mPhoneId + "]" +  str);
     }
 
     private void writeUpdatedEmergencyNumberListMetrics(
@@ -1186,6 +1258,56 @@
     }
 
     /**
+     * @return {@code true} if emergency numbers sourced from modem/config should be ignored.
+     * {@code false} if emergency numbers sourced from modem/config should not be ignored.
+     */
+    @VisibleForTesting
+    public boolean shouldModemConfigEmergencyNumbersBeIgnored() {
+        return mResources.getBoolean(com.android.internal.R.bool
+                .ignore_modem_config_emergency_numbers);
+    }
+
+    /**
+     * @return {@code true} if emergency number routing from the android emergency number
+     * database should be ignored.
+     * {@code false} if emergency number routing from the android emergency number database
+     * should not be ignored.
+     */
+    @VisibleForTesting
+    public boolean shouldEmergencyNumberRoutingFromDbBeIgnored() {
+        return mResources.getBoolean(com.android.internal.R.bool
+                .ignore_emergency_number_routing_from_db);
+    }
+
+
+    /**
+     * @return {@code true} if determining of Urns & Service Categories while merging duplicate
+     * numbers should be ignored.
+     * {@code false} if determining of Urns & Service Categories while merging duplicate
+     * numbers should not be ignored.
+     */
+    @VisibleForTesting
+    public boolean shouldDeterminingOfUrnsAndCategoriesWhileMergingIgnored() {
+        // TODO: Device config
+        return false;
+    }
+
+    /**
+     * Captures the consolidated emergency numbers list and returns the array of
+     * {@link PersistAtomsProto.EmergencyNumber}.
+     */
+    public PersistAtomsProto.EmergencyNumbersInfo[] getEmergencyNumbersProtoArray() {
+        int otaVersion = Math.max(0, getEmergencyNumberOtaDbVersion());
+        int assetVersion = Math.max(0, getEmergencyNumberDbVersion());
+        boolean isDbRoutingIgnored = shouldEmergencyNumberRoutingFromDbBeIgnored();
+        List<EmergencyNumber> emergencyNumberList = getEmergencyNumberList();
+        logd("log emergency number list=" + emergencyNumberList + " for otaVersion=" + otaVersion
+                + ", assetVersion=" + assetVersion + ", isDbRoutingIgnored=" + isDbRoutingIgnored);
+        return EmergencyNumberStats.getInstance().convertEmergencyNumbersListToProto(
+                emergencyNumberList, assetVersion, otaVersion, isDbRoutingIgnored);
+    }
+
+    /**
      * Dump Emergency Number List info in the tracking
      *
      * @param fd FileDescriptor
@@ -1194,9 +1316,6 @@
      */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
-        ipw.println(" Hal Version:" + mPhone.getHalVersion());
-        ipw.println(" ========================================= ");
-
         ipw.println(" Country Iso:" + getEmergencyCountryIso());
         ipw.println(" ========================================= ");
 
@@ -1233,11 +1352,6 @@
         ipw.decreaseIndent();
         ipw.println(" ========================================= ");
 
-        int slotId = SubscriptionController.getInstance().getSlotIndex(mPhone.getSubId());
-        String ecclist = (slotId <= 0) ? "ril.ecclist" : ("ril.ecclist" + slotId);
-        ipw.println(" ril.ecclist: " + SystemProperties.get(ecclist, ""));
-        ipw.println(" ========================================= ");
-
         ipw.println("Emergency Number List for Phone" + "(" + mPhone.getPhoneId() + ")");
         ipw.increaseIndent();
         ipw.println(getEmergencyNumberList());
diff --git a/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java b/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
new file mode 100644
index 0000000..96cd880
--- /dev/null
+++ b/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
@@ -0,0 +1,1208 @@
+/*
+ * Copyright (C) 2022 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.emergency;
+
+import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_CALLBACK;
+import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_NONE;
+import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WWAN;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.sysprop.TelephonyProperties;
+import android.telephony.Annotation.DisconnectCauses;
+import android.telephony.CarrierConfigManager;
+import android.telephony.DisconnectCause;
+import android.telephony.EmergencyRegResult;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.emergency.EmergencyNumber;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.VisibleForTesting;
+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.PhoneFactory;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.data.PhoneSwitcher;
+import com.android.telephony.Rlog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
+
+/**
+ * Tracks the emergency call state and notifies listeners of changes to the emergency mode.
+ */
+public class EmergencyStateTracker {
+
+    private static final String TAG = "EmergencyStateTracker";
+
+    /**
+     * 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;
+    /** 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;
+
+    /** The emergency types used when setting the emergency mode on modem. */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "EMERGENCY_TYPE_",
+            value = {
+                    EMERGENCY_TYPE_CALL,
+                    EMERGENCY_TYPE_SMS})
+    public @interface EmergencyType {}
+
+    /** Indicates the emergency type is call. */
+    public static final int EMERGENCY_TYPE_CALL = 1;
+    /** Indicates the emergency type is SMS. */
+    public static final int EMERGENCY_TYPE_SMS = 2;
+
+    private static EmergencyStateTracker INSTANCE = null;
+
+    private final Context mContext;
+    private final CarrierConfigManager mConfigManager;
+    private final Handler mHandler;
+    private final boolean mIsSuplDdsSwitchRequiredForEmergencyCall;
+    private final PowerManager.WakeLock mWakeLock;
+    private RadioOnHelper mRadioOnHelper;
+    @EmergencyConstants.EmergencyMode
+    private int mEmergencyMode = MODE_EMERGENCY_NONE;
+    private boolean mWasEmergencyModeSetOnModem;
+    private EmergencyRegResult mLastEmergencyRegResult;
+    private boolean mIsEmergencyModeInProgress;
+    private boolean mIsEmergencyCallStartedDuringEmergencySms;
+
+    /** For emergency calls */
+    private final long mEcmExitTimeoutMs;
+    // A runnable which is used to automatically exit from Ecm after a period of time.
+    private final Runnable mExitEcmRunnable = this::exitEmergencyCallbackMode;
+    // Tracks emergency calls by callId that have reached {@link Call.State#ACTIVE}.
+    private final Set<String> mActiveEmergencyCalls = new ArraySet<>();
+    private Phone mPhone;
+    // Tracks ongoing emergency callId to handle a second emergency call
+    private String mOngoingCallId;
+    // 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;
+    private boolean mIsInEmergencyCall;
+    private boolean mIsInEcm;
+    private boolean mIsTestEmergencyNumber;
+    private Runnable mOnEcmExitCompleteRunnable;
+
+    /** For emergency SMS */
+    private final Set<String> mOngoingEmergencySmsIds = new ArraySet<>();
+    private Phone mSmsPhone;
+    private CompletableFuture<Integer> mSmsEmergencyModeFuture;
+    private boolean mIsTestEmergencyNumberForSms;
+
+    /**
+     * Listens for Emergency Callback Mode state change intents
+     */
+    private final BroadcastReceiver mEcmExitReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(
+                    TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED)) {
+
+                boolean isInEcm = intent.getBooleanExtra(
+                        TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false);
+                Rlog.d(TAG, "Received ACTION_EMERGENCY_CALLBACK_MODE_CHANGED isInEcm = " + isInEcm);
+
+                // If we exit ECM mode, notify all connections.
+                if (!isInEcm) {
+                    exitEmergencyCallbackMode();
+                }
+            }
+        }
+    };
+
+    /** PhoneFactory Dependencies for testing. */
+    @VisibleForTesting
+    public interface PhoneFactoryProxy {
+        Phone[] getPhones();
+    }
+
+    private PhoneFactoryProxy mPhoneFactoryProxy = PhoneFactory::getPhones;
+
+    /** PhoneSwitcher dependencies for testing. */
+    @VisibleForTesting
+    public interface PhoneSwitcherProxy {
+
+        PhoneSwitcher getPhoneSwitcher();
+    }
+
+    private PhoneSwitcherProxy mPhoneSwitcherProxy = PhoneSwitcher::getInstance;
+
+    /**
+     * TelephonyManager dependencies for testing.
+     */
+    @VisibleForTesting
+    public interface TelephonyManagerProxy {
+        int getPhoneCount();
+    }
+
+    private final TelephonyManagerProxy mTelephonyManagerProxy;
+
+    private static class TelephonyManagerProxyImpl implements TelephonyManagerProxy {
+        private final TelephonyManager mTelephonyManager;
+
+
+        TelephonyManagerProxyImpl(Context context) {
+            mTelephonyManager = new TelephonyManager(context);
+        }
+
+        @Override
+        public int getPhoneCount() {
+            return mTelephonyManager.getActiveModemCount();
+        }
+    }
+
+    /**
+     * Return the handler for testing.
+     */
+    @VisibleForTesting
+    public Handler getHandler() {
+        return mHandler;
+    }
+
+    @VisibleForTesting
+    public static final int MSG_SET_EMERGENCY_MODE_DONE = 1;
+    @VisibleForTesting
+    public static final int MSG_EXIT_EMERGENCY_MODE_DONE = 2;
+    @VisibleForTesting
+    public static final int MSG_SET_EMERGENCY_CALLBACK_MODE_DONE = 3;
+
+    private class MyHandler extends Handler {
+
+        MyHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SET_EMERGENCY_MODE_DONE: {
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    Integer emergencyType = (Integer) ar.userObj;
+                    Rlog.v(TAG, "MSG_SET_EMERGENCY_MODE_DONE for "
+                            + emergencyTypeToString(emergencyType));
+                    if (ar.exception == null) {
+                        mLastEmergencyRegResult = (EmergencyRegResult) ar.result;
+                    } else {
+                        mLastEmergencyRegResult = null;
+                        Rlog.w(TAG, "LastEmergencyRegResult not set. AsyncResult.exception: "
+                                + ar.exception);
+                    }
+                    setEmergencyModeInProgress(false);
+
+                    if (emergencyType == EMERGENCY_TYPE_CALL) {
+                        setIsInEmergencyCall(true);
+                        completeEmergencyMode(emergencyType);
+
+                        // 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 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);
+                                // Restore call phone for further use.
+                                mPhone = phone;
+
+                                if (!isSamePhone(mPhone, mSmsPhone)) {
+                                    completeEmergencyMode(emergencyType,
+                                            DisconnectCause.OUTGOING_EMERGENCY_CALL_PLACED);
+                                }
+                            } else {
+                                completeEmergencyMode(emergencyType);
+                            }
+                            break;
+                        } else {
+                            completeEmergencyMode(emergencyType);
+                        }
+
+                        if (mIsEmergencyCallStartedDuringEmergencySms) {
+                            mIsEmergencyCallStartedDuringEmergencySms = false;
+                            turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL,
+                                    mIsTestEmergencyNumber);
+                        }
+                    }
+                    break;
+                }
+                case MSG_EXIT_EMERGENCY_MODE_DONE: {
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    Integer emergencyType = (Integer) ar.userObj;
+                    Rlog.v(TAG, "MSG_EXIT_EMERGENCY_MODE_DONE for "
+                            + emergencyTypeToString(emergencyType));
+                    setEmergencyModeInProgress(false);
+
+                    if (emergencyType == EMERGENCY_TYPE_CALL) {
+                        setIsInEmergencyCall(false);
+                        if (mOnEcmExitCompleteRunnable != null) {
+                            mOnEcmExitCompleteRunnable.run();
+                            mOnEcmExitCompleteRunnable = null;
+                        }
+                    } else if (emergencyType == EMERGENCY_TYPE_SMS) {
+                        if (mIsEmergencyCallStartedDuringEmergencySms) {
+                            mIsEmergencyCallStartedDuringEmergencySms = false;
+                            turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL,
+                                    mIsTestEmergencyNumber);
+                        }
+                    }
+                    break;
+                }
+                case MSG_SET_EMERGENCY_CALLBACK_MODE_DONE: {
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    Integer emergencyType = (Integer) ar.userObj;
+                    Rlog.v(TAG, "MSG_SET_EMERGENCY_CALLBACK_MODE_DONE for "
+                            + emergencyTypeToString(emergencyType));
+                    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);
+                    }
+                    break;
+                }
+                default:
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Creates the EmergencyStateTracker singleton instance.
+     *
+     * @param context                                 The context of the application.
+     * @param isSuplDdsSwitchRequiredForEmergencyCall Whether gnss supl requires default data for
+     *                                                emergency call.
+     */
+    public static void make(Context context, boolean isSuplDdsSwitchRequiredForEmergencyCall) {
+        if (INSTANCE == null) {
+            INSTANCE = new EmergencyStateTracker(context, Looper.myLooper(),
+                    isSuplDdsSwitchRequiredForEmergencyCall);
+        }
+    }
+
+    /**
+     * Returns the singleton instance of EmergencyStateTracker.
+     *
+     * @return {@link EmergencyStateTracker} instance.
+     */
+    public static EmergencyStateTracker getInstance() {
+        if (INSTANCE == null) {
+            throw new IllegalStateException("EmergencyStateTracker is not ready!");
+        }
+        return INSTANCE;
+    }
+
+    /**
+     * Initializes EmergencyStateTracker.
+     */
+    private EmergencyStateTracker(Context context, Looper looper,
+            boolean isSuplDdsSwitchRequiredForEmergencyCall) {
+        mEcmExitTimeoutMs = DEFAULT_ECM_EXIT_TIMEOUT_MS;
+        mContext = context;
+        mHandler = new MyHandler(looper);
+        mIsSuplDdsSwitchRequiredForEmergencyCall = isSuplDdsSwitchRequiredForEmergencyCall;
+
+        PowerManager pm = context.getSystemService(PowerManager.class);
+        mWakeLock = (pm != null) ? pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                "telephony:" + TAG) : null;
+        mConfigManager = context.getSystemService(CarrierConfigManager.class);
+
+        // Register receiver for ECM exit.
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
+        context.registerReceiver(mEcmExitReceiver, filter, null, mHandler);
+        mTelephonyManagerProxy = new TelephonyManagerProxyImpl(context);
+    }
+
+    /**
+     * Initializes EmergencyStateTracker with injections for testing.
+     *
+     * @param context                                 The context of the application.
+     * @param looper                                  The {@link Looper} of the application.
+     * @param isSuplDdsSwitchRequiredForEmergencyCall Whether gnss supl requires default data for
+     *                                                emergency call.
+     * @param phoneFactoryProxy                       The {@link PhoneFactoryProxy} to be injected.
+     * @param phoneSwitcherProxy                      The {@link PhoneSwitcherProxy} to be injected.
+     * @param telephonyManagerProxy                   The {@link TelephonyManagerProxy} to be
+     *                                                injected.
+     * @param radioOnHelper                           The {@link RadioOnHelper} to be injected.
+     */
+    @VisibleForTesting
+    public EmergencyStateTracker(Context context, Looper looper,
+            boolean isSuplDdsSwitchRequiredForEmergencyCall, PhoneFactoryProxy phoneFactoryProxy,
+            PhoneSwitcherProxy phoneSwitcherProxy, TelephonyManagerProxy telephonyManagerProxy,
+            RadioOnHelper radioOnHelper, long ecmExitTimeoutMs) {
+        mContext = context;
+        mHandler = new MyHandler(looper);
+        mIsSuplDdsSwitchRequiredForEmergencyCall = isSuplDdsSwitchRequiredForEmergencyCall;
+        mPhoneFactoryProxy = phoneFactoryProxy;
+        mPhoneSwitcherProxy = phoneSwitcherProxy;
+        mTelephonyManagerProxy = telephonyManagerProxy;
+        mRadioOnHelper = radioOnHelper;
+        mEcmExitTimeoutMs = ecmExitTimeoutMs;
+        mWakeLock = null; // Don't declare a wakelock in tests
+        mConfigManager = context.getSystemService(CarrierConfigManager.class);
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
+        context.registerReceiver(mEcmExitReceiver, filter, null, mHandler);
+    }
+
+    /**
+     * Starts the process of an emergency call.
+     *
+     * <p>
+     * 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 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);
+
+        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;
+                mIsTestEmergencyNumber = isTestEmergencyNumber;
+                return CompletableFuture.completedFuture(DisconnectCause.NOT_DISCONNECTED);
+            }
+
+            Rlog.e(TAG, "startEmergencyCall failed. Existing emergency call in progress.");
+            return CompletableFuture.completedFuture(DisconnectCause.ERROR_UNSPECIFIED);
+        }
+
+        mCallEmergencyModeFuture = new CompletableFuture<>();
+
+        if (mSmsPhone != null) {
+            mIsEmergencyCallStartedDuringEmergencySms = true;
+            // Case1) While exiting the emergency mode on the other phone,
+            // the emergency mode for this call will be restarted after the exit complete.
+            // Case2) While entering the emergency mode on the other phone,
+            // 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);
+            }
+
+            mPhone = phone;
+            mOngoingCallId = callId;
+            mIsTestEmergencyNumber = isTestEmergencyNumber;
+            return mCallEmergencyModeFuture;
+        }
+
+        mPhone = phone;
+        mOngoingCallId = callId;
+        mIsTestEmergencyNumber = isTestEmergencyNumber;
+        turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL, mIsTestEmergencyNumber);
+        return mCallEmergencyModeFuture;
+    }
+
+    /**
+     * Ends emergency call.
+     *
+     * <p>
+     * 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.
+     */
+    public void endCall(@NonNull String callId) {
+        boolean wasActive = mActiveEmergencyCalls.remove(callId);
+
+        if (Objects.equals(mOngoingCallId, callId)) {
+            mOngoingCallId = null;
+        }
+
+        if (wasActive && mActiveEmergencyCalls.isEmpty()
+                && isEmergencyCallbackModeSupported()) {
+            enterEmergencyCallbackMode();
+
+            if (mOngoingCallId == null) {
+                mIsEmergencyCallStartedDuringEmergencySms = false;
+                mCallEmergencyModeFuture = null;
+            }
+        } else if (mOngoingCallId == 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);
+                }
+            } else {
+                exitEmergencyMode(mPhone, EMERGENCY_TYPE_CALL);
+                clearEmergencyCallInfo();
+            }
+        }
+    }
+
+    private void clearEmergencyCallInfo() {
+        mEmergencyCallDomain = NetworkRegistrationInfo.DOMAIN_UNKNOWN;
+        mIsTestEmergencyNumber = false;
+        mIsEmergencyCallStartedDuringEmergencySms = false;
+        mCallEmergencyModeFuture = null;
+        mOngoingCallId = null;
+        mPhone = null;
+    }
+
+    private void switchDdsAndSetEmergencyMode(Phone phone, @EmergencyType int emergencyType) {
+        switchDdsDelayed(phone, result -> {
+            Rlog.i(TAG, "switchDdsDelayed: result = " + result);
+            if (!result) {
+                // DDS Switch timed out/failed, but continue with call as it may still succeed.
+                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);
+        });
+    }
+
+    /**
+     * Triggers modem to set new emergency mode.
+     *
+     * @param phone the {@code Phone} to set the emergency mode on modem.
+     * @param emergencyType the emergency type to identify an emergency call or SMS.
+     * @param mode the new emergency mode.
+     * @param msg the message to be sent once mode has been set.
+     */
+    private void setEmergencyMode(Phone phone, @EmergencyType int emergencyType,
+            @EmergencyConstants.EmergencyMode int mode, int msg) {
+        Rlog.i(TAG, "setEmergencyMode from " + mEmergencyMode + " to " + mode + " for "
+                + emergencyTypeToString(emergencyType));
+
+        if (mEmergencyMode == mode) {
+            return;
+        }
+        mEmergencyMode = mode;
+        setEmergencyModeInProgress(true);
+
+        Message m = mHandler.obtainMessage(msg, Integer.valueOf(emergencyType));
+        if ((mIsTestEmergencyNumber && emergencyType == EMERGENCY_TYPE_CALL)
+                || (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
+            AsyncResult.forMessage(m, null, null);
+            // Ensure that we do not accidentally block indefinitely when trying to validate test
+            // emergency numbers
+            m.sendToTarget();
+            return;
+        }
+
+        mWasEmergencyModeSetOnModem = true;
+        phone.setEmergencyMode(mode, m);
+    }
+
+    private void completeEmergencyMode(@EmergencyType int emergencyType) {
+        completeEmergencyMode(emergencyType, DisconnectCause.NOT_DISCONNECTED);
+    }
+
+    private void completeEmergencyMode(@EmergencyType int emergencyType,
+            @DisconnectCauses int result) {
+        if (emergencyType == EMERGENCY_TYPE_CALL) {
+            if (mCallEmergencyModeFuture != null && !mCallEmergencyModeFuture.isDone()) {
+                mCallEmergencyModeFuture.complete(result);
+            }
+
+            if (result != DisconnectCause.NOT_DISCONNECTED) {
+                clearEmergencyCallInfo();
+            }
+        } else if (emergencyType == EMERGENCY_TYPE_SMS) {
+            if (mSmsEmergencyModeFuture != null && !mSmsEmergencyModeFuture.isDone()) {
+                mSmsEmergencyModeFuture.complete(result);
+            }
+
+            if (result != DisconnectCause.NOT_DISCONNECTED) {
+                clearEmergencySmsInfo();
+            }
+        }
+    }
+
+    /**
+     * Checks if the device is currently in the emergency mode or not.
+     */
+    @VisibleForTesting
+    public boolean isInEmergencyMode() {
+        return mEmergencyMode != MODE_EMERGENCY_NONE;
+    }
+
+    /**
+     * Sets the flag to inidicate whether setting the emergency mode on modem is in progress or not.
+     */
+    private void setEmergencyModeInProgress(boolean isEmergencyModeInProgress) {
+        mIsEmergencyModeInProgress = isEmergencyModeInProgress;
+    }
+
+    /**
+     * Checks whether setting the emergency mode on modem is in progress or not.
+     */
+    private boolean isEmergencyModeInProgress() {
+        return mIsEmergencyModeInProgress;
+    }
+
+    /**
+     * Notifies external app listeners of emergency mode changes.
+     *
+     * @param isInEmergencyCall a flag to indicate whether there is an active emergency call.
+     */
+    private void setIsInEmergencyCall(boolean isInEmergencyCall) {
+        mIsInEmergencyCall = isInEmergencyCall;
+    }
+
+    /**
+     * Checks if there is an ongoing emergency call.
+     *
+     * @return true if in emergency call
+     */
+    public boolean isInEmergencyCall() {
+        return mIsInEmergencyCall;
+    }
+
+    /**
+     * Triggers modem to exit emergency mode.
+     *
+     * @param phone the {@code Phone} to exit the emergency mode.
+     * @param emergencyType the emergency type to identify an emergency call or SMS.
+     */
+    private void exitEmergencyMode(Phone phone, @EmergencyType int emergencyType) {
+        Rlog.i(TAG, "exitEmergencyMode for " + emergencyTypeToString(emergencyType));
+
+        if (emergencyType == EMERGENCY_TYPE_CALL) {
+            if (mSmsPhone != null && isSamePhone(phone, mSmsPhone)) {
+                // Waits for exiting the emergency mode until the emergency SMS is ended.
+                Rlog.i(TAG, "exitEmergencyMode: waits for emergency SMS end.");
+                setIsInEmergencyCall(false);
+                return;
+            }
+        } else if (emergencyType == EMERGENCY_TYPE_SMS) {
+            if (mPhone != null && isSamePhone(phone, mPhone)) {
+                // Waits for exiting the emergency mode until the emergency call is ended.
+                Rlog.i(TAG, "exitEmergencyMode: waits for emergency call end.");
+                return;
+            }
+        }
+
+        if (mEmergencyMode == MODE_EMERGENCY_NONE) {
+            return;
+        }
+        mEmergencyMode = MODE_EMERGENCY_NONE;
+        setEmergencyModeInProgress(true);
+
+        Message m = mHandler.obtainMessage(
+                MSG_EXIT_EMERGENCY_MODE_DONE, Integer.valueOf(emergencyType));
+        if (!mWasEmergencyModeSetOnModem) {
+            Rlog.d(TAG, "Emergency mode was not set on modem: Skipping exiting emergency mode.");
+            // Send back a response for the command, but with null information
+            AsyncResult.forMessage(m, null, null);
+            // Ensure that we do not accidentally block indefinitely when trying to validate
+            // the exit condition.
+            m.sendToTarget();
+            return;
+        }
+
+        mWasEmergencyModeSetOnModem = false;
+        phone.exitEmergencyMode(m);
+    }
+
+    /** Returns last {@link EmergencyRegResult} as set by {@code setEmergencyMode()}. */
+    public EmergencyRegResult getEmergencyRegResult() {
+        return mLastEmergencyRegResult;
+    }
+
+    /**
+     * 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 onEmergencyTransportChanged(@EmergencyType int emergencyType,
+            @EmergencyConstants.EmergencyMode int mode) {
+        if (mHandler.getLooper().isCurrentThread()) {
+            Phone phone = null;
+            if (emergencyType == EMERGENCY_TYPE_CALL) {
+                phone = mPhone;
+            } else if (emergencyType == EMERGENCY_TYPE_SMS) {
+                phone = mSmsPhone;
+            }
+
+            if (phone != null) {
+                setEmergencyMode(phone, emergencyType, mode, MSG_SET_EMERGENCY_MODE_DONE);
+            }
+        } else {
+            mHandler.post(() -> {
+                onEmergencyTransportChanged(emergencyType, mode);
+            });
+        }
+    }
+
+    /**
+     * 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
+     */
+    public void onEmergencyCallDomainUpdated(int phoneType, String callId) {
+        Rlog.d(TAG, "domain update for callId: " + callId);
+        int domain = -1;
+        switch(phoneType) {
+            case (PhoneConstants.PHONE_TYPE_CDMA_LTE):
+                //fallthrough
+            case (PhoneConstants.PHONE_TYPE_GSM):
+                //fallthrough
+            case (PhoneConstants.PHONE_TYPE_CDMA): {
+                domain = NetworkRegistrationInfo.DOMAIN_CS;
+                break;
+            }
+            case (PhoneConstants.PHONE_TYPE_IMS): {
+                domain = NetworkRegistrationInfo.DOMAIN_PS;
+                break;
+            }
+            default: {
+                Rlog.w(TAG, "domain updated: Unexpected phoneType:" + phoneType);
+            }
+        }
+        if (mEmergencyCallDomain == domain) return;
+        Rlog.i(TAG, "domain updated: from " + mEmergencyCallDomain + " to " + domain);
+        mEmergencyCallDomain = domain;
+    }
+
+    /**
+     * Handles emergency call state change.
+     *
+     * @param state the new call state
+     * @param callId the callId whose state has changed
+     */
+    public void onEmergencyCallStateChanged(Call.State state, String callId) {
+        if (state == Call.State.ACTIVE) {
+            mActiveEmergencyCalls.add(callId);
+        }
+    }
+
+    /**
+     * Returns {@code true} if device and carrier support emergency callback mode.
+     */
+    private boolean isEmergencyCallbackModeSupported() {
+        return getConfig(mPhone.getSubId(),
+                CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_CALLBACK_MODE_SUPPORTED_BOOL,
+                DEFAULT_EMERGENCY_CALLBACK_MODE_SUPPORTED);
+    }
+
+    /**
+     * Trigger entry into emergency callback mode.
+     */
+    private void enterEmergencyCallbackMode() {
+        Rlog.d(TAG, "enter ECBM");
+        setIsInEmergencyCall(false);
+        // Check if not in ECM already.
+        if (!isInEcm()) {
+            setIsInEcm(true);
+            if (!mPhone.getUnitTestMode()) {
+                TelephonyProperties.in_ecm_mode(true);
+            }
+
+            // Notify listeners of the entrance to ECM.
+            sendEmergencyCallbackModeChange();
+            if (isInImsEcm()) {
+                // emergency call registrants are not notified of new emergency call until entering
+                // 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);
+        }
+    }
+
+    /**
+     * Exits emergency callback mode and notifies relevant listeners.
+     */
+    public void exitEmergencyCallbackMode() {
+        Rlog.d(TAG, "exit ECBM");
+        // Remove pending exit ECM runnable, if any.
+        mHandler.removeCallbacks(mExitEcmRunnable);
+
+        if (isInEcm()) {
+            setIsInEcm(false);
+            if (!mPhone.getUnitTestMode()) {
+                TelephonyProperties.in_ecm_mode(false);
+            }
+
+            // 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());
+                }
+            }
+
+            GsmCdmaPhone gsmCdmaPhone = (GsmCdmaPhone) mPhone;
+            // Send intents that ECM has changed.
+            sendEmergencyCallbackModeChange();
+            gsmCdmaPhone.notifyEmergencyCallRegistrants(false);
+
+            // Exit emergency mode on modem.
+            exitEmergencyMode(gsmCdmaPhone, EMERGENCY_TYPE_CALL);
+        }
+
+        mEmergencyCallDomain = NetworkRegistrationInfo.DOMAIN_UNKNOWN;
+        mIsTestEmergencyNumber = false;
+        mPhone = null;
+    }
+
+    /**
+     * Exits emergency callback mode and triggers runnable after exit response is received.
+     */
+    public void exitEmergencyCallbackMode(Runnable onComplete) {
+        mOnEcmExitCompleteRunnable = onComplete;
+        exitEmergencyCallbackMode();
+    }
+
+    /**
+     * Sends intents that emergency callback mode changed.
+     */
+    private void sendEmergencyCallbackModeChange() {
+        Rlog.d(TAG, "sendEmergencyCallbackModeChange: isInEcm=" + isInEcm());
+
+        Intent intent = new Intent(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
+        intent.putExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, isInEcm());
+        SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
+        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
+    /**
+     * Returns {@code true} if currently in emergency callback mode.
+     *
+     * <p>
+     * This is a period where the phone should be using as little power as possible and be ready to
+     * receive an incoming call from the emergency operator.
+     */
+    public boolean isInEcm() {
+        return mIsInEcm;
+    }
+
+    /**
+     * Sets the emergency callback mode state.
+     *
+     * @param isInEcm {@code true} if currently in emergency callback mode, {@code false} otherwise.
+     */
+    private void setIsInEcm(boolean isInEcm) {
+        mIsInEcm = isInEcm;
+    }
+
+    /**
+     * Returns {@code true} if currently in emergency callback mode over PS
+     */
+    public boolean isInImsEcm() {
+        return mEmergencyCallDomain == NetworkRegistrationInfo.DOMAIN_PS && isInEcm();
+    }
+
+    /**
+     * Returns {@code true} if currently in emergency callback mode over CS
+     */
+    public boolean isInCdmaEcm() {
+        // Phone can be null in the case where we are not actively tracking an emergency call.
+        if (mPhone == null) return false;
+        // Ensure that this method doesn't return true when we are attached to GSM.
+        return mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA
+                && mEmergencyCallDomain == NetworkRegistrationInfo.DOMAIN_CS && isInEcm();
+    }
+
+    /**
+     * Starts the process of an emergency SMS.
+     *
+     * @param phone the {@code Phone} on which to process the emergency SMS.
+     * @param smsId the SMS id on which to process the emergency SMS.
+     * @param isTestEmergencyNumber whether this is a test emergency number.
+     * @return A {@code CompletableFuture} that results in {@code DisconnectCause.NOT_DISCONNECTED}
+     *         if the emergency SMS is successfully started.
+     */
+    public CompletableFuture<Integer> startEmergencySms(@NonNull Phone phone, @NonNull String smsId,
+            boolean isTestEmergencyNumber) {
+        Rlog.i(TAG, "startEmergencySms: phoneId=" + phone.getPhoneId() + ", smsId=" + smsId);
+
+        // When an emergency call is in progress, it checks whether an emergency call is already in
+        // progress on the different phone.
+        if (mPhone != null && !isSamePhone(mPhone, phone)) {
+            Rlog.e(TAG, "Emergency call is in progress on the other slot.");
+            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.
+        if (mSmsPhone != null && !isSamePhone(mSmsPhone, phone)) {
+            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,
+        // this new request will not be allowed.
+        if (mSmsPhone != null && isInEmergencyMode() && isEmergencyModeInProgress()) {
+            Rlog.e(TAG, "Existing emergency SMS is in progress.");
+            return CompletableFuture.completedFuture(DisconnectCause.ERROR_UNSPECIFIED);
+        }
+
+        mSmsPhone = phone;
+        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);
+        }
+
+        mSmsEmergencyModeFuture = new CompletableFuture<>();
+        if (!isInEmergencyMode()) {
+            setEmergencyMode(mSmsPhone, EMERGENCY_TYPE_SMS, MODE_EMERGENCY_WWAN,
+                    MSG_SET_EMERGENCY_MODE_DONE);
+        }
+        return mSmsEmergencyModeFuture;
+    }
+
+    /**
+     * Ends an emergency SMS.
+     * This should be called once an emergency SMS is sent.
+     *
+     * @param smsId the SMS id on which to end the emergency SMS.
+     * @param emergencyNumber the emergency number which was used for the emergency SMS.
+     */
+    public void endSms(@NonNull String smsId, EmergencyNumber emergencyNumber) {
+        mOngoingEmergencySmsIds.remove(smsId);
+
+        // If the outgoing emergency SMSs are empty, we can try to exit the emergency mode.
+        if (mOngoingEmergencySmsIds.isEmpty()) {
+            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);
+                }
+            } else {
+                exitEmergencyMode(mSmsPhone, EMERGENCY_TYPE_SMS);
+            }
+
+            clearEmergencySmsInfo();
+        }
+    }
+
+    private void clearEmergencySmsInfo() {
+        mOngoingEmergencySmsIds.clear();
+        mIsTestEmergencyNumberForSms = false;
+        mSmsEmergencyModeFuture = null;
+        mSmsPhone = null;
+    }
+
+    /**
+     * Returns {@code true} if any phones from PhoneFactory have radio on.
+     */
+    private boolean isRadioOn() {
+        boolean result = false;
+        for (Phone phone : mPhoneFactoryProxy.getPhones()) {
+            result |= phone.isRadioOn();
+        }
+        return result;
+    }
+
+    /**
+     * Returns {@code true} if airplane mode is on.
+     */
+    private boolean isAirplaneModeOn(Context context) {
+        return Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.AIRPLANE_MODE_ON, 0) > 0;
+    }
+
+    /**
+     * Ensures that the radio is switched on and that DDS is switched for emergency call/SMS.
+     *
+     * <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.
+     * Once domain selection is finished, the actual emergency mode will be set when
+     * onEmergencyTransportChanged() is called.
+     *
+     * @param phone the {@code Phone} for the emergency call/SMS.
+     * @param emergencyType the emergency type to identify an emergency call or SMS.
+     * @param isTestEmergencyNumber a flag to inidicate whether the emergency call/SMS uses the test
+     *                              emergency number.
+     */
+    private void turnOnRadioAndSwitchDds(Phone phone, @EmergencyType int emergencyType,
+            boolean isTestEmergencyNumber) {
+        final boolean isAirplaneModeOn = isAirplaneModeOn(mContext);
+        boolean needToTurnOnRadio = !isRadioOn() || isAirplaneModeOn;
+
+        if (needToTurnOnRadio) {
+            Rlog.i(TAG, "turnOnRadioAndSwitchDds: phoneId=" + phone.getPhoneId() + " for "
+                    + emergencyTypeToString(emergencyType));
+            if (mRadioOnHelper == null) {
+                mRadioOnHelper = new RadioOnHelper(mContext);
+            }
+
+            mRadioOnHelper.triggerRadioOnAndListen(new RadioOnStateListener.Callback() {
+                @Override
+                public void onComplete(RadioOnStateListener listener, boolean isRadioReady) {
+                    if (!isRadioReady) {
+                        // Could not turn radio on
+                        Rlog.e(TAG, "Failed to turn on radio.");
+                        completeEmergencyMode(emergencyType, DisconnectCause.POWER_OFF);
+                    } else {
+                        switchDdsAndSetEmergencyMode(phone, emergencyType);
+                    }
+                }
+
+                @Override
+                public boolean isOkToCall(Phone phone, int serviceState, boolean imsVoiceCapable) {
+                    // We currently only look to make sure that the radio is on before dialing. We
+                    // should be able to make emergency calls at any time after the radio has been
+                    // powered on and isn't in the UNAVAILABLE state, even if it is reporting the
+                    // OUT_OF_SERVICE state.
+                    return phone.getServiceStateTracker().isRadioOn();
+                }
+
+                @Override
+                public boolean onTimeout(Phone phone, int serviceState, boolean imsVoiceCapable) {
+                    return true;
+                }
+            }, !isTestEmergencyNumber, phone, isTestEmergencyNumber, 0);
+        } else {
+            switchDdsAndSetEmergencyMode(phone, emergencyType);
+        }
+    }
+
+    /**
+     * If needed, block until the default data is switched for outgoing emergency call, or
+     * timeout expires.
+     *
+     * @param phone            The Phone to switch the DDS on.
+     * @param completeConsumer The consumer to call once the default data subscription has been
+     *                         switched, provides {@code true} result if the switch happened
+     *                         successfully or {@code false} if the operation timed out/failed.
+     */
+    @VisibleForTesting
+    public void switchDdsDelayed(Phone phone, Consumer<Boolean> completeConsumer) {
+        if (phone == null) {
+            // Do not block indefinitely.
+            completeConsumer.accept(false);
+        }
+        try {
+            // Waiting for PhoneSwitcher to complete the operation.
+            CompletableFuture<Boolean> future = possiblyOverrideDefaultDataForEmergencyCall(phone);
+            // In the case that there is an issue or bug in PhoneSwitcher logic, do not wait
+            // indefinitely for the future to complete. Instead, set a timeout that will complete
+            // the future as to not block the outgoing call indefinitely.
+            CompletableFuture<Boolean> timeout = new CompletableFuture<>();
+            mHandler.postDelayed(() -> timeout.complete(false), DEFAULT_DATA_SWITCH_TIMEOUT_MS);
+            // Also ensure that the Consumer is completed on the main thread.
+            CompletableFuture<Void> unused = future.acceptEitherAsync(timeout, completeConsumer,
+                    mHandler::post);
+        } catch (Exception e) {
+            Rlog.w(TAG, "switchDdsDelayed - exception= " + e.getMessage());
+        }
+    }
+
+    /**
+     * If needed, block until Default Data subscription is switched for outgoing emergency call.
+     *
+     * <p>
+     * In some cases, we need to try to switch the Default Data subscription before placing the
+     * emergency call on DSDS devices. This includes the following situation: - The modem does not
+     * support processing GNSS SUPL requests on the non-default data subscription. For some carriers
+     * that do not provide a control plane fallback mechanism, the SUPL request will be dropped and
+     * we will not be able to get the user's location for the emergency call. In this case, we need
+     * to swap default data temporarily.
+     *
+     * @param phone Evaluates whether or not the default data should be moved to the phone
+     *              specified. Should not be null.
+     */
+    private CompletableFuture<Boolean> possiblyOverrideDefaultDataForEmergencyCall(
+            @NonNull Phone phone) {
+        int phoneCount = mTelephonyManagerProxy.getPhoneCount();
+        // Do not override DDS if this is a single SIM device.
+        if (phoneCount <= PhoneConstants.MAX_PHONE_COUNT_SINGLE_SIM) {
+            return CompletableFuture.completedFuture(Boolean.TRUE);
+        }
+
+        // Do not switch Default data if this device supports emergency SUPL on non-DDS.
+        if (!mIsSuplDdsSwitchRequiredForEmergencyCall) {
+            Rlog.d(TAG, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS, does not "
+                    + "require DDS switch.");
+            return CompletableFuture.completedFuture(Boolean.TRUE);
+        }
+
+        // Only override default data if we are IN_SERVICE already.
+        if (!isAvailableForEmergencyCalls(phone)) {
+            Rlog.d(TAG, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS");
+            return CompletableFuture.completedFuture(Boolean.TRUE);
+        }
+
+        // Only override default data if we are not roaming, we do not want to switch onto a network
+        // that only supports data plane only (if we do not know).
+        boolean isRoaming = phone.getServiceState().getVoiceRoaming();
+        // In some roaming conditions, we know the roaming network doesn't support control plane
+        // fallback even though the home operator does. For these operators we will need to do a DDS
+        // switch anyway to make sure the SUPL request doesn't fail.
+        boolean roamingNetworkSupportsControlPlaneFallback = true;
+        String[] dataPlaneRoamPlmns = getConfig(phone.getSubId(),
+                CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY);
+        if (dataPlaneRoamPlmns != null && Arrays.asList(dataPlaneRoamPlmns)
+                .contains(phone.getServiceState().getOperatorNumeric())) {
+            roamingNetworkSupportsControlPlaneFallback = false;
+        }
+        if (isRoaming && roamingNetworkSupportsControlPlaneFallback) {
+            Rlog.d(TAG, "possiblyOverrideDefaultDataForEmergencyCall: roaming network is assumed "
+                    + "to support CP fallback, not switching DDS.");
+            return CompletableFuture.completedFuture(Boolean.TRUE);
+        }
+        // Do not try to swap default data if we support CS fallback or it is assumed that the
+        // roaming network supports control plane fallback, we do not want to introduce a lag in
+        // emergency call setup time if possible.
+        final boolean supportsCpFallback = getConfig(phone.getSubId(),
+                CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
+                CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_ONLY)
+                != CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY;
+        if (supportsCpFallback && roamingNetworkSupportsControlPlaneFallback) {
+            Rlog.d(TAG, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS, carrier "
+                    + "supports CP fallback.");
+            return CompletableFuture.completedFuture(Boolean.TRUE);
+        }
+
+        // Get extension time, may be 0 for some carriers that support ECBM as well. Use
+        // CarrierConfig default if format fails.
+        int extensionTime = 0;
+        try {
+            extensionTime = Integer.parseInt(getConfig(phone.getSubId(),
+                    CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "0"));
+        } catch (NumberFormatException e) {
+            // Just use default.
+        }
+        CompletableFuture<Boolean> modemResultFuture = new CompletableFuture<>();
+        try {
+            Rlog.d(TAG, "possiblyOverrideDefaultDataForEmergencyCall: overriding DDS for "
+                    + extensionTime + "seconds");
+            mPhoneSwitcherProxy.getPhoneSwitcher().overrideDefaultDataForEmergency(
+                    phone.getPhoneId(), extensionTime, modemResultFuture);
+            // Catch all exceptions, we want to continue with emergency call if possible.
+        } catch (Exception e) {
+            Rlog.w(TAG,
+                    "possiblyOverrideDefaultDataForEmergencyCall: exception = " + e.getMessage());
+            modemResultFuture = CompletableFuture.completedFuture(Boolean.FALSE);
+        }
+        return modemResultFuture;
+    }
+
+    // Helper functions for easy CarrierConfigManager access
+    private String getConfig(int subId, String key, String defVal) {
+        return getConfigBundle(subId, key).getString(key, defVal);
+    }
+    private int getConfig(int subId, String key, int defVal) {
+        return getConfigBundle(subId, key).getInt(key, defVal);
+    }
+    private String[] getConfig(int subId, String key) {
+        return getConfigBundle(subId, key).getStringArray(key);
+    }
+    private boolean getConfig(int subId, String key, boolean defVal) {
+        return getConfigBundle(subId, key).getBoolean(key, defVal);
+    }
+    private PersistableBundle getConfigBundle(int subId, String key) {
+        if (mConfigManager == null) return new PersistableBundle();
+        return mConfigManager.getConfigForSubId(subId, key);
+    }
+
+    /**
+     * Returns true if the state of the Phone is IN_SERVICE or available for emergency calling only.
+     */
+    private boolean isAvailableForEmergencyCalls(Phone phone) {
+        return ServiceState.STATE_IN_SERVICE == phone.getServiceState().getState()
+                || phone.getServiceState().isEmergencyOnly();
+    }
+
+    /**
+     * Checks whether both {@code Phone}s are same or not.
+     */
+    private static boolean isSamePhone(Phone p1, Phone p2) {
+        return p1 != null && p2 != null && (p1.getPhoneId() == p2.getPhoneId());
+    }
+
+    private static String emergencyTypeToString(@EmergencyType int emergencyType) {
+        switch (emergencyType) {
+            case EMERGENCY_TYPE_CALL: return "CALL";
+            case EMERGENCY_TYPE_SMS: return "SMS";
+            default: return "UNKNOWN";
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java b/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java
new file mode 100644
index 0000000..9c4ebfa
--- /dev/null
+++ b/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2022 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.emergency;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.telephony.IIntegerConsumer;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.satellite.SatelliteController;
+import com.android.telephony.Rlog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper class that implements special behavior related to emergency calls or making phone calls
+ * when the radio is in the POWER_OFF STATE. Specifically, this class handles the case of the user
+ * trying to dial an emergency number while the radio is off (i.e. the device is in airplane mode)
+ * or a normal number while the radio is off (because of the device is on Bluetooth), by turning the
+ * radio back on, waiting for it to come up, and then retrying the call.
+ */
+public class RadioOnHelper implements RadioOnStateListener.Callback {
+
+    private static final String TAG = "RadioOnStateListener";
+
+    private final Context mContext;
+    private RadioOnStateListener.Callback mCallback;
+    private List<RadioOnStateListener> mListeners;
+    private List<RadioOnStateListener> mInProgressListeners;
+    private boolean mIsRadioReady;
+
+    public RadioOnHelper(Context context) {
+        mContext = context;
+        mInProgressListeners = new ArrayList<>(2);
+    }
+
+    private void setupListeners() {
+        if (mListeners == null) {
+            mListeners = new ArrayList<>(2);
+        }
+        int activeModems = TelephonyManager.from(mContext).getActiveModemCount();
+        // Add new listeners if active modem count increased.
+        while (mListeners.size() < activeModems) {
+            mListeners.add(new RadioOnStateListener());
+        }
+        // Clean up listeners if active modem count decreased.
+        while (mListeners.size() > activeModems) {
+            mListeners.get(mListeners.size() - 1).cleanup();
+            mListeners.remove(mListeners.size() - 1);
+        }
+    }
+
+    /**
+     * Starts the "turn on radio" sequence. This is the (single) external API of the RadioOnHelper
+     * class.
+     *
+     * This method kicks off the following sequence:
+     * - Power on the radio for each Phone and disable the satellite modem
+     * - Listen for events telling us the radio has come up or the satellite modem is disabled.
+     * - Retry if we've gone a significant amount of time without any response.
+     * - Finally, clean up any leftover state.
+     *
+     * This method is safe to call from any thread, since it simply posts a message to the
+     * RadioOnHelper's handler (thus ensuring that the rest of the sequence is entirely serialized,
+     * and runs on the main looper.)
+     */
+    public void triggerRadioOnAndListen(RadioOnStateListener.Callback callback,
+            boolean forEmergencyCall, Phone phoneForEmergencyCall, boolean isTestEmergencyNumber,
+            int emergencyTimeoutIntervalMillis) {
+        setupListeners();
+        mCallback = callback;
+        mInProgressListeners.clear();
+        mIsRadioReady = false;
+        for (int i = 0; i < TelephonyManager.from(mContext).getActiveModemCount(); i++) {
+            Phone phone = PhoneFactory.getPhone(i);
+            if (phone == null) {
+                continue;
+            }
+
+            int timeoutCallbackInterval = (forEmergencyCall && phone == phoneForEmergencyCall)
+                    ? emergencyTimeoutIntervalMillis : 0;
+            mInProgressListeners.add(mListeners.get(i));
+            mListeners.get(i).waitForRadioOn(phone, this, forEmergencyCall, forEmergencyCall
+                    && phone == phoneForEmergencyCall, timeoutCallbackInterval);
+        }
+        powerOnRadio(forEmergencyCall, phoneForEmergencyCall, isTestEmergencyNumber);
+        if (SatelliteController.getInstance().isSatelliteEnabled()) {
+            powerOffSatellite(phoneForEmergencyCall);
+        }
+    }
+
+    /**
+     * Attempt to power on the radio (i.e. take the device out of airplane mode). We'll eventually
+     * get an onServiceStateChanged() callback when the radio successfully comes up.
+     */
+    private void powerOnRadio(boolean forEmergencyCall, Phone phoneForEmergencyCall,
+            boolean isTestEmergencyNumber) {
+
+        // Always try to turn on the radio here independent of APM setting - if we got here in the
+        // first place, the radio is off independent of APM setting.
+        for (Phone phone : PhoneFactory.getPhones()) {
+            Rlog.d(TAG, "powerOnRadio, enabling Radio");
+            if (isTestEmergencyNumber) {
+                phone.setRadioPowerOnForTestEmergencyCall(phone == phoneForEmergencyCall);
+            } else {
+                phone.setRadioPower(true, forEmergencyCall, phone == phoneForEmergencyCall,
+                        false);
+            }
+        }
+
+        // If airplane mode is on, we turn it off the same way that the Settings activity turns it
+        // off to keep the setting in sync.
+        if (Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.AIRPLANE_MODE_ON, 0) > 0) {
+            Rlog.d(TAG, "==> Turning off airplane mode for emergency call.");
+
+            // Change the system setting
+            Settings.Global.putInt(mContext.getContentResolver(),
+                    Settings.Global.AIRPLANE_MODE_ON, 0);
+
+            // Post the broadcast intend for change in airplane mode TODO: We really should not be
+            // in charge of sending this broadcast. If changing the setting is sufficient to trigger
+            // all of the rest of the logic, then that should also trigger the broadcast intent.
+            Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+            intent.putExtra("state", false);
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+        }
+    }
+
+    /**
+     * Attempt to power off the satellite modem. We'll eventually get an
+     * onSatelliteModemStateChanged() callback when the satellite modem is successfully disabled.
+     */
+    private void powerOffSatellite(Phone phoneForEmergencyCall) {
+        SatelliteController satelliteController = SatelliteController.getInstance();
+        satelliteController.requestSatelliteEnabled(phoneForEmergencyCall.getSubId(),
+                false /* enableSatellite */, false /* enableDemoMode */,
+                new IIntegerConsumer.Stub() {
+                    @Override
+                    public void accept(int result) {
+
+                    }
+                });
+    }
+
+    /**
+     * This method is called from multiple Listeners on the Main Looper. Synchronization is not
+     * necessary.
+     */
+    @Override
+    public void onComplete(RadioOnStateListener listener, boolean isRadioReady) {
+        mIsRadioReady |= isRadioReady;
+        mInProgressListeners.remove(listener);
+        if (mCallback != null && mInProgressListeners.isEmpty()) {
+            mCallback.onComplete(null, mIsRadioReady);
+        }
+    }
+
+    @Override
+    public boolean isOkToCall(Phone phone, int serviceState, boolean imsVoiceCapable) {
+        return (mCallback == null)
+                ? false : mCallback.isOkToCall(phone, serviceState, imsVoiceCapable);
+    }
+
+    @Override
+    public boolean onTimeout(Phone phone, int serviceState, boolean imsVoiceCapable) {
+        return (mCallback == null)
+                ? false : mCallback.onTimeout(phone, serviceState, imsVoiceCapable);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/emergency/RadioOnStateListener.java b/src/java/com/android/internal/telephony/emergency/RadioOnStateListener.java
new file mode 100644
index 0000000..d61c146
--- /dev/null
+++ b/src/java/com/android/internal/telephony/emergency/RadioOnStateListener.java
@@ -0,0 +1,584 @@
+/*
+ * Copyright (C) 2022 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.emergency;
+
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.telephony.satellite.ISatelliteStateCallback;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.telephony.IIntegerConsumer;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.satellite.SatelliteController;
+import com.android.telephony.Rlog;
+
+import java.util.Locale;
+
+/**
+ * Helper class that listens to a Phone's radio state and sends an onComplete callback when we
+ * return true for isOkToCall.
+ */
+public class RadioOnStateListener {
+
+    public interface Callback {
+        /**
+         * Receives the result of the RadioOnStateListener's attempt to turn on the radio
+         * and turn off the satellite modem.
+         */
+        void onComplete(RadioOnStateListener listener, boolean isRadioReady);
+
+        /**
+         * Returns whether or not this phone is ok to call.
+         * If it is, onComplete will be called shortly after.
+         *
+         * @param phone The Phone associated.
+         * @param serviceState The service state of that phone.
+         * @param imsVoiceCapable The IMS voice capability of that phone.
+         * @return {@code true} if this phone is ok to call. Otherwise, {@code false}.
+         */
+        boolean isOkToCall(Phone phone, int serviceState, boolean imsVoiceCapable);
+
+        /**
+         * Returns whether or not this phone is ok to call.
+         * This callback will be called when timeout happens.
+         * If this returns {@code true}, onComplete will be called shortly after.
+         * Otherwise, a new timer will be started again to keep waiting for next timeout.
+         * The timeout interval will be passed to {@link #waitForRadioOn()} when registering
+         * this callback.
+         *
+         * @param phone The Phone associated.
+         * @param serviceState The service state of that phone.
+         * @param imsVoiceCapable The IMS voice capability of that phone.
+         * @return {@code true} if this phone is ok to call. Otherwise, {@code false}.
+         */
+        boolean onTimeout(Phone phone, int serviceState, boolean imsVoiceCapable);
+    }
+
+    private static final String TAG = "RadioOnStateListener";
+
+    // Number of times to retry the call, and time between retry attempts.
+    // not final for testing
+    private static int MAX_NUM_RETRIES = 5;
+    // not final for testing
+    private static long TIME_BETWEEN_RETRIES_MILLIS = 5000; // msec
+
+    // Handler message codes; see handleMessage()
+    private static final int MSG_START_SEQUENCE = 1;
+    @VisibleForTesting
+    public static final int MSG_SERVICE_STATE_CHANGED = 2;
+    private static final int MSG_RETRY_TIMEOUT = 3;
+    @VisibleForTesting
+    public static final int MSG_RADIO_ON = 4;
+    public static final int MSG_RADIO_OFF_OR_NOT_AVAILABLE = 5;
+    public static final int MSG_IMS_CAPABILITY_CHANGED = 6;
+    public static final int MSG_TIMEOUT_ONTIMEOUT_CALLBACK = 7;
+    @VisibleForTesting
+    public static final int MSG_SATELLITE_ENABLED_CHANGED = 8;
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_START_SEQUENCE:
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        Phone phone = (Phone) args.arg1;
+                        RadioOnStateListener.Callback callback =
+                                (RadioOnStateListener.Callback) args.arg2;
+                        boolean forEmergencyCall = (boolean) args.arg3;
+                        boolean isSelectedPhoneForEmergencyCall = (boolean) args.arg4;
+                        int onTimeoutCallbackInterval = args.argi1;
+                        startSequenceInternal(phone, callback, forEmergencyCall,
+                                isSelectedPhoneForEmergencyCall, onTimeoutCallbackInterval);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                case MSG_SERVICE_STATE_CHANGED:
+                    onServiceStateChanged((ServiceState) ((AsyncResult) msg.obj).result);
+                    break;
+                case MSG_RADIO_ON:
+                    onRadioOn();
+                    break;
+                case MSG_RADIO_OFF_OR_NOT_AVAILABLE:
+                    registerForRadioOn();
+                    break;
+                case MSG_RETRY_TIMEOUT:
+                    onRetryTimeout();
+                    break;
+                case MSG_IMS_CAPABILITY_CHANGED:
+                    onImsCapabilityChanged();
+                    break;
+                case MSG_TIMEOUT_ONTIMEOUT_CALLBACK:
+                    onTimeoutCallbackTimeout();
+                    break;
+                case MSG_SATELLITE_ENABLED_CHANGED:
+                    onSatelliteEnabledChanged();
+                    break;
+                default:
+                    Rlog.w(TAG, String.format(Locale.getDefault(),
+                        "handleMessage: unexpected message: %d.", msg.what));
+                    break;
+            }
+        }
+    };
+
+    private final ISatelliteStateCallback mSatelliteCallback = new ISatelliteStateCallback.Stub() {
+        @Override
+        public void onSatelliteModemStateChanged(int state) {
+            mHandler.obtainMessage(MSG_SATELLITE_ENABLED_CHANGED).sendToTarget();
+        }
+    };
+
+    private Callback mCallback; // The callback to notify upon completion.
+    private Phone mPhone; // The phone that will attempt to place the call.
+    // SatelliteController instance to check whether satellite has been disabled.
+    private SatelliteController mSatelliteController;
+    private boolean mForEmergencyCall; // Whether radio is being turned on for emergency call.
+    // Whether this phone is selected to place emergency call. Can be true only if
+    // mForEmergencyCall is true.
+    private boolean mSelectedPhoneForEmergencyCall;
+    private int mNumRetriesSoFar;
+    private int mOnTimeoutCallbackInterval; // the interval between onTimeout callbacks
+
+    /**
+     * Starts the "wait for radio" sequence. This is the (single) external API of the
+     * RadioOnStateListener class.
+     *
+     * This method kicks off the following sequence:
+     * - Listen for the service state change event telling us the radio has come up.
+     * - Listen for the satellite state changed event telling us the satellite service is disabled.
+     * - Retry if we've gone {@link #TIME_BETWEEN_RETRIES_MILLIS} without any response from the
+     *   radio.
+     * - Finally, clean up any leftover state.
+     *
+     * This method is safe to call from any thread, since it simply posts a message to the
+     * RadioOnStateListener's handler (thus ensuring that the rest of the sequence is entirely
+     * serialized, and runs only on the handler thread.)
+     */
+    public void waitForRadioOn(Phone phone, Callback callback,
+            boolean forEmergencyCall, boolean isSelectedPhoneForEmergencyCall,
+            int onTimeoutCallbackInterval) {
+        Rlog.d(TAG, "waitForRadioOn: Phone " + phone.getPhoneId());
+
+        if (mPhone != null) {
+            // If there already is an ongoing request, ignore the new one!
+            return;
+        }
+
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = phone;
+        args.arg2 = callback;
+        args.arg3 = forEmergencyCall;
+        args.arg4 = isSelectedPhoneForEmergencyCall;
+        args.argi1 = onTimeoutCallbackInterval;
+        mHandler.obtainMessage(MSG_START_SEQUENCE, args).sendToTarget();
+    }
+
+    /**
+     * Actual implementation of waitForRadioOn(), guaranteed to run on the handler thread.
+     *
+     * @see #waitForRadioOn
+     */
+    private void startSequenceInternal(Phone phone, Callback callback,
+            boolean forEmergencyCall, boolean isSelectedPhoneForEmergencyCall,
+            int onTimeoutCallbackInterval) {
+        Rlog.d(TAG, "startSequenceInternal: Phone " + phone.getPhoneId());
+        mSatelliteController = SatelliteController.getInstance();
+
+        // First of all, clean up any state left over from a prior RadioOn call sequence. This
+        // ensures that we'll behave sanely if another startTurnOnRadioSequence() comes in while
+        // we're already in the middle of the sequence.
+        cleanup();
+
+        mPhone = phone;
+        mCallback = callback;
+        mForEmergencyCall = forEmergencyCall;
+        mSelectedPhoneForEmergencyCall = isSelectedPhoneForEmergencyCall;
+        mOnTimeoutCallbackInterval = onTimeoutCallbackInterval;
+
+        registerForServiceStateChanged();
+        // Register for RADIO_OFF to handle cases where emergency call is dialed before
+        // we receive UNSOL_RESPONSE_RADIO_STATE_CHANGED with RADIO_OFF.
+        registerForRadioOff();
+        if (mSatelliteController.isSatelliteEnabled()) {
+            // Register for satellite modem state changed to notify when satellite is disabled.
+            registerForSatelliteEnabledChanged();
+        }
+        // Next step: when the SERVICE_STATE_CHANGED or SATELLITE_ENABLED_CHANGED event comes in,
+        // we'll retry the call; see onServiceStateChanged() and onSatelliteEnabledChanged().
+        // But also, just in case, start a timer to make sure we'll retry the call even if the
+        // SERVICE_STATE_CHANGED or SATELLITE_ENABLED_CHANGED events never come in for some reason.
+        startRetryTimer();
+        registerForImsCapabilityChanged();
+        startOnTimeoutCallbackTimer();
+    }
+
+    private void onImsCapabilityChanged() {
+        if (mPhone == null) {
+            return;
+        }
+
+        boolean imsVoiceCapable = mPhone.isVoiceOverCellularImsEnabled();
+
+        Rlog.d(TAG, String.format("onImsCapabilityChanged, capable = %s, Phone = %s",
+                imsVoiceCapable, mPhone.getPhoneId()));
+
+        if (isOkToCall(mPhone.getServiceState().getState(), imsVoiceCapable)) {
+            Rlog.d(TAG, "onImsCapabilityChanged: ok to call!");
+
+            onComplete(true);
+            cleanup();
+        } else {
+            // The IMS capability changed, but we're still not ready to call yet.
+            Rlog.d(TAG, "onImsCapabilityChanged: not ready to call yet, keep waiting.");
+        }
+    }
+
+    private void onTimeoutCallbackTimeout() {
+        if (mPhone == null) {
+            return;
+        }
+
+        if (onTimeout(mPhone.getServiceState().getState(),
+                  mPhone.isVoiceOverCellularImsEnabled())) {
+            Rlog.d(TAG, "onTimeout: ok to call!");
+
+            onComplete(true);
+            cleanup();
+        } else if (mNumRetriesSoFar > MAX_NUM_RETRIES) {
+            Rlog.w(TAG, "onTimeout: Hit MAX_NUM_RETRIES; giving up.");
+            cleanup();
+        } else {
+            Rlog.d(TAG, "onTimeout: not ready to call yet, keep waiting.");
+            startOnTimeoutCallbackTimer();
+        }
+    }
+
+    /**
+     * Handles the SERVICE_STATE_CHANGED event. This event tells us that the radio state has changed
+     * and is probably coming up. We can now check to see if the conditions are met to place the
+     * call with {@link Callback#isOkToCall}
+     */
+    private void onServiceStateChanged(ServiceState state) {
+        if (mPhone == null) {
+            return;
+        }
+        Rlog.d(TAG, String.format("onServiceStateChanged(), new state = %s, Phone = %s", state,
+                mPhone.getPhoneId()));
+
+        // Possible service states:
+        // - STATE_IN_SERVICE        // Normal operation
+        // - STATE_OUT_OF_SERVICE    // Still searching for an operator to register to,
+        //                           // or no radio signal
+        // - STATE_EMERGENCY_ONLY    // Only emergency numbers are allowed; currently not used
+        // - STATE_POWER_OFF         // Radio is explicitly powered off (airplane mode)
+
+        if (isOkToCall(state.getState(), mPhone.isVoiceOverCellularImsEnabled())) {
+            // Woo hoo! It's OK to actually place the call.
+            Rlog.d(TAG, "onServiceStateChanged: ok to call!");
+
+            onComplete(true);
+            cleanup();
+        } else {
+            // The service state changed, but we're still not ready to call yet.
+            Rlog.d(TAG, "onServiceStateChanged: not ready to call yet, keep waiting.");
+        }
+    }
+
+    private void onRadioOn() {
+        if (mPhone == null) {
+            return;
+        }
+        ServiceState state = mPhone.getServiceState();
+        Rlog.d(TAG, String.format("onRadioOn, state = %s, Phone = %s", state, mPhone.getPhoneId()));
+        if (isOkToCall(state.getState(), mPhone.isVoiceOverCellularImsEnabled())) {
+            onComplete(true);
+            cleanup();
+        } else {
+            Rlog.d(TAG, "onRadioOn: not ready to call yet, keep waiting.");
+        }
+    }
+
+    private void onSatelliteEnabledChanged() {
+        if (mPhone == null) {
+            return;
+        }
+        if (isOkToCall(mPhone.getServiceState().getState(),
+                mPhone.isVoiceOverCellularImsEnabled())) {
+            onComplete(true);
+            cleanup();
+        } else {
+            Rlog.d(TAG, "onSatelliteEnabledChanged: not ready to call yet, keep waiting.");
+        }
+    }
+
+    /**
+     * Callback to see if it is okay to call yet, given the current conditions.
+     */
+    private boolean isOkToCall(int serviceState, boolean imsVoiceCapable) {
+        return (mCallback == null)
+                ? false : mCallback.isOkToCall(mPhone, serviceState, imsVoiceCapable);
+    }
+
+    /**
+     * Callback to see if it is okay to call yet, given the current conditions.
+     */
+    private boolean onTimeout(int serviceState, boolean imsVoiceCapable) {
+        return (mCallback == null)
+                ? false : mCallback.onTimeout(mPhone, serviceState, imsVoiceCapable);
+    }
+
+    /**
+     * Handles the retry timer expiring.
+     */
+    private void onRetryTimeout() {
+        if (mPhone == null) {
+            return;
+        }
+        int serviceState = mPhone.getServiceState().getState();
+        Rlog.d(TAG,
+                String.format(Locale.getDefault(),
+                        "onRetryTimeout():  phone state = %s, service state = %d, retries = %d.",
+                        mPhone.getState(), serviceState, mNumRetriesSoFar));
+
+        // - If we're actually in a call, we've succeeded.
+        // - Otherwise, if the radio is now on, that means we successfully got out of airplane mode
+        //   but somehow didn't get the service state change event. In that case, try to place the
+        //   call.
+        // - If the radio is still powered off, try powering it on again.
+
+        if (isOkToCall(serviceState, mPhone.isVoiceOverCellularImsEnabled())) {
+            Rlog.d(TAG, "onRetryTimeout: Radio is on. Cleaning up.");
+
+            // Woo hoo -- we successfully got out of airplane mode.
+            onComplete(true);
+            cleanup();
+        } else {
+            // Uh oh; we've waited the full TIME_BETWEEN_RETRIES_MILLIS and the radio is still not
+            // powered-on. Try again.
+
+            mNumRetriesSoFar++;
+            Rlog.d(TAG, "mNumRetriesSoFar is now " + mNumRetriesSoFar);
+
+            if (mNumRetriesSoFar > MAX_NUM_RETRIES) {
+                if (mHandler.hasMessages(MSG_TIMEOUT_ONTIMEOUT_CALLBACK)) {
+                    Rlog.w(TAG, "Hit MAX_NUM_RETRIES; waiting onTimeout callback");
+                    return;
+                }
+                Rlog.w(TAG, "Hit MAX_NUM_RETRIES; giving up.");
+                cleanup();
+            } else {
+                Rlog.d(TAG, "Trying (again) to turn the radio on and satellite modem off.");
+                mPhone.setRadioPower(true, mForEmergencyCall, mSelectedPhoneForEmergencyCall,
+                        false);
+                if (mSatelliteController.isSatelliteEnabled()) {
+                    mSatelliteController.requestSatelliteEnabled(mPhone.getSubId(),
+                            false /* enableSatellite */, false /* enableDemoMode */,
+                            new IIntegerConsumer.Stub() {
+                                @Override
+                                public void accept(int result) {
+                                    mHandler.obtainMessage(MSG_SATELLITE_ENABLED_CHANGED)
+                                            .sendToTarget();
+                                }
+                            });
+                }
+                startRetryTimer();
+            }
+        }
+    }
+
+    /**
+     * Clean up when done with the whole sequence: either after successfully turning on the radio,
+     * or after bailing out because of too many failures.
+     *
+     * The exact cleanup steps are:
+     * - Notify callback if we still hadn't sent it a response.
+     * - Double-check that we're not still registered for any telephony events
+     * - Clean up any extraneous handler messages (like retry timeouts) still in the queue
+     *
+     * Basically this method guarantees that there will be no more activity from the
+     * RadioOnStateListener until someone kicks off the whole sequence again with another call to
+     * {@link #waitForRadioOn}
+     *
+     * TODO: Do the work for the comment below: Note we don't call this method simply after a
+     * successful call to placeCall(), since it's still possible the call will disconnect very
+     * quickly with an OUT_OF_SERVICE error.
+     */
+    public void cleanup() {
+        Rlog.d(TAG, "cleanup()");
+
+        // This will send a failure call back if callback has yet to be invoked. If the callback was
+        // already invoked, it's a no-op.
+        onComplete(false);
+
+        unregisterForServiceStateChanged();
+        unregisterForRadioOff();
+        unregisterForRadioOn();
+        unregisterForSatelliteEnabledChanged();
+        cancelRetryTimer();
+        unregisterForImsCapabilityChanged();
+
+        // Used for unregisterForServiceStateChanged() so we null it out here instead.
+        mPhone = null;
+        mNumRetriesSoFar = 0;
+        mOnTimeoutCallbackInterval = 0;
+    }
+
+    private void startRetryTimer() {
+        cancelRetryTimer();
+        mHandler.sendEmptyMessageDelayed(MSG_RETRY_TIMEOUT, TIME_BETWEEN_RETRIES_MILLIS);
+    }
+
+    private void cancelRetryTimer() {
+        mHandler.removeMessages(MSG_RETRY_TIMEOUT);
+    }
+
+    private void registerForServiceStateChanged() {
+        // Unregister first, just to make sure we never register ourselves twice. (We need this
+        // because Phone.registerForServiceStateChanged() does not prevent multiple registration of
+        // the same handler.)
+        unregisterForServiceStateChanged();
+        mPhone.registerForServiceStateChanged(mHandler, MSG_SERVICE_STATE_CHANGED, null);
+    }
+
+    private void unregisterForServiceStateChanged() {
+        // This method is safe to call even if we haven't set mPhone yet.
+        if (mPhone != null) {
+            mPhone.unregisterForServiceStateChanged(mHandler); // Safe even if unnecessary
+        }
+        mHandler.removeMessages(MSG_SERVICE_STATE_CHANGED); // Clean up any pending messages too
+    }
+
+    private void registerForRadioOff() {
+        mPhone.mCi.registerForOffOrNotAvailable(mHandler, MSG_RADIO_OFF_OR_NOT_AVAILABLE, null);
+    }
+
+    private void unregisterForRadioOff() {
+        // This method is safe to call even if we haven't set mPhone yet.
+        if (mPhone != null) {
+            mPhone.mCi.unregisterForOffOrNotAvailable(mHandler); // Safe even if unnecessary
+        }
+        mHandler.removeMessages(MSG_RADIO_OFF_OR_NOT_AVAILABLE); // Clean up any pending messages
+    }
+
+    private void registerForRadioOn() {
+        unregisterForRadioOff();
+        mPhone.mCi.registerForOn(mHandler, MSG_RADIO_ON, null);
+    }
+
+    private void unregisterForRadioOn() {
+        // This method is safe to call even if we haven't set mPhone yet.
+        if (mPhone != null) {
+            mPhone.mCi.unregisterForOn(mHandler); // Safe even if unnecessary
+        }
+        mHandler.removeMessages(MSG_RADIO_ON); // Clean up any pending messages too
+    }
+
+    private void registerForSatelliteEnabledChanged() {
+        mSatelliteController.registerForSatelliteModemStateChanged(
+                mPhone.getSubId(), mSatelliteCallback);
+    }
+
+    private void unregisterForSatelliteEnabledChanged() {
+        int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        if (mPhone != null) {
+            subId = mPhone.getSubId();
+        }
+        mSatelliteController.unregisterForSatelliteModemStateChanged(subId, mSatelliteCallback);
+        mHandler.removeMessages(MSG_SATELLITE_ENABLED_CHANGED);
+    }
+
+    private void registerForImsCapabilityChanged() {
+        unregisterForImsCapabilityChanged();
+        mPhone.getServiceStateTracker()
+                .registerForImsCapabilityChanged(mHandler, MSG_IMS_CAPABILITY_CHANGED, null);
+    }
+
+    private void unregisterForImsCapabilityChanged() {
+        if (mPhone != null) {
+            mPhone.getServiceStateTracker()
+                    .unregisterForImsCapabilityChanged(mHandler);
+        }
+        mHandler.removeMessages(MSG_IMS_CAPABILITY_CHANGED);
+    }
+
+    private void startOnTimeoutCallbackTimer() {
+        Rlog.d(TAG, "startOnTimeoutCallbackTimer: mOnTimeoutCallbackInterval="
+                + mOnTimeoutCallbackInterval);
+        mHandler.removeMessages(MSG_TIMEOUT_ONTIMEOUT_CALLBACK);
+        if (mOnTimeoutCallbackInterval > 0) {
+            mHandler.sendEmptyMessageDelayed(MSG_TIMEOUT_ONTIMEOUT_CALLBACK,
+                    mOnTimeoutCallbackInterval);
+        }
+    }
+
+    private void onComplete(boolean isRadioReady) {
+        if (mCallback != null) {
+            Callback tempCallback = mCallback;
+            mCallback = null;
+            tempCallback.onComplete(this, isRadioReady);
+        }
+    }
+
+    @VisibleForTesting
+    public Handler getHandler() {
+        return mHandler;
+    }
+
+    @VisibleForTesting
+    public void setMaxNumRetries(int retries) {
+        MAX_NUM_RETRIES = retries;
+    }
+
+    @VisibleForTesting
+    public void setTimeBetweenRetriesMillis(long timeMs) {
+        TIME_BETWEEN_RETRIES_MILLIS = timeMs;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (o == null || !getClass().equals(o.getClass()))
+            return false;
+
+        RadioOnStateListener that = (RadioOnStateListener) o;
+
+        if (mNumRetriesSoFar != that.mNumRetriesSoFar) {
+            return false;
+        }
+        if (mCallback != null ? !mCallback.equals(that.mCallback) : that.mCallback != null) {
+            return false;
+        }
+        return mPhone != null ? mPhone.equals(that.mPhone) : that.mPhone == null;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 7;
+        hash = 31 * hash + mNumRetriesSoFar;
+        hash = 31 * hash + (mCallback == null ? 0 : mCallback.hashCode());
+        hash = 31 * hash + (mPhone == null ? 0 : mPhone.hashCode());
+        return hash;
+    }
+}
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccCardController.java b/src/java/com/android/internal/telephony/euicc/EuiccCardController.java
index bb42b2a..2f73c91 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccCardController.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccCardController.java
@@ -39,8 +39,6 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.PhoneFactory;
-import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.uicc.UiccCard;
 import com.android.internal.telephony.uicc.UiccController;
@@ -416,7 +414,7 @@
         // if there is no iccid enabled on this port, return null.
         if (TextUtils.isEmpty(iccId)) {
             try {
-                callback.onComplete(EuiccCardManager.RESULT_PROFILE_NOT_FOUND, null);
+                callback.onComplete(EuiccCardManager.RESULT_PROFILE_DOES_NOT_EXIST, null);
             } catch (RemoteException exception) {
                 loge("getEnabledProfile callback failure.", exception);
             }
@@ -652,14 +650,8 @@
             @Override
             public void onResult(Void result) {
                 Log.i(TAG, "Request subscription info list refresh after delete.");
-                if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-                    SubscriptionManagerService.getInstance().updateEmbeddedSubscriptions(
-                            List.of(mUiccController.convertToPublicCardId(cardId)), null);
-                } else {
-                    SubscriptionController.getInstance()
-                            .requestEmbeddedSubscriptionInfoListRefresh(
-                                    mUiccController.convertToPublicCardId(cardId));
-                }
+                SubscriptionManagerService.getInstance().updateEmbeddedSubscriptions(
+                        List.of(mUiccController.convertToPublicCardId(cardId)), null);
                 try {
                     callback.onComplete(EuiccCardManager.RESULT_OK);
                 } catch (RemoteException exception) {
@@ -709,14 +701,8 @@
             @Override
             public void onResult(Void result) {
                 Log.i(TAG, "Request subscription info list refresh after reset memory.");
-                if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-                    SubscriptionManagerService.getInstance().updateEmbeddedSubscriptions(
-                            List.of(mUiccController.convertToPublicCardId(cardId)), null);
-                } else {
-                    SubscriptionController.getInstance()
-                            .requestEmbeddedSubscriptionInfoListRefresh(
-                                    mUiccController.convertToPublicCardId(cardId));
-                }
+                SubscriptionManagerService.getInstance().updateEmbeddedSubscriptions(
+                        List.of(mUiccController.convertToPublicCardId(cardId)), null);
                 try {
                     callback.onComplete(EuiccCardManager.RESULT_OK);
                 } catch (RemoteException exception) {
@@ -1203,14 +1189,8 @@
             @Override
             public void onResult(byte[] result) {
                 Log.i(TAG, "Request subscription info list refresh after install.");
-                if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-                    SubscriptionManagerService.getInstance().updateEmbeddedSubscriptions(
-                            List.of(mUiccController.convertToPublicCardId(cardId)), null);
-                } else {
-                    SubscriptionController.getInstance()
-                            .requestEmbeddedSubscriptionInfoListRefresh(
-                                    mUiccController.convertToPublicCardId(cardId));
-                }
+                SubscriptionManagerService.getInstance().updateEmbeddedSubscriptions(
+                        List.of(mUiccController.convertToPublicCardId(cardId)), null);
                 try {
                     callback.onComplete(EuiccCardManager.RESULT_OK, result);
                 } catch (RemoteException exception) {
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccConnector.java b/src/java/com/android/internal/telephony/euicc/EuiccConnector.java
index 974acf9..c417a34 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccConnector.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccConnector.java
@@ -101,7 +101,8 @@
      * true or onServiceDisconnected is called (and no package change has occurred which should
      * force us to reestablish the binding).
      */
-    private static final int BIND_TIMEOUT_MILLIS = 30000;
+    @VisibleForTesting
+    static final int BIND_TIMEOUT_MILLIS = 30000;
 
     /**
      * Maximum amount of idle time to hold the binding while in {@link ConnectedState}. After this,
@@ -225,6 +226,8 @@
     static class GetMetadataRequest {
         DownloadableSubscription mSubscription;
         boolean mForceDeactivateSim;
+        boolean mSwitchAfterDownload;
+        int mPortIndex;
         GetMetadataCommandCallback mCallback;
     }
 
@@ -389,6 +392,9 @@
         mSm = (SubscriptionManager)
                 context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
 
+        // TODO(b/239277548): Disable debug logging after analysing this bug.
+        setDbg(true);
+
         // Unavailable/Available both monitor for package changes and update mSelectedComponent but
         // do not need to adjust the binding.
         mUnavailableState = new UnavailableState();
@@ -444,13 +450,15 @@
 
     /** Asynchronously fetch metadata for the given downloadable subscription. */
     @VisibleForTesting(visibility = PACKAGE)
-    public void getDownloadableSubscriptionMetadata(int cardId,
-            DownloadableSubscription subscription,
+    public void getDownloadableSubscriptionMetadata(int cardId, int portIndex,
+            DownloadableSubscription subscription, boolean switchAfterDownload,
             boolean forceDeactivateSim, GetMetadataCommandCallback callback) {
         GetMetadataRequest request =
                 new GetMetadataRequest();
         request.mSubscription = subscription;
         request.mForceDeactivateSim = forceDeactivateSim;
+        request.mSwitchAfterDownload = switchAfterDownload;
+        request.mPortIndex = portIndex;
         request.mCallback = callback;
         sendMessage(CMD_GET_DOWNLOADABLE_SUBSCRIPTION_METADATA, cardId, 0 /* arg2 */, request);
     }
@@ -549,6 +557,11 @@
                 callback);
     }
 
+    @VisibleForTesting
+    public final IEuiccService getBinder() {
+        return mEuiccService;
+    }
+
     /**
      * State in which no EuiccService is available.
      *
@@ -686,6 +699,7 @@
                 }
                 return HANDLED;
             } else if (message.what == CMD_CONNECT_TIMEOUT) {
+                unbind();
                 transitionTo(mAvailableState);
                 return HANDLED;
             } else if (isEuiccCommand(message.what)) {
@@ -749,7 +763,9 @@
                         case CMD_GET_DOWNLOADABLE_SUBSCRIPTION_METADATA: {
                             GetMetadataRequest request = (GetMetadataRequest) message.obj;
                             mEuiccService.getDownloadableSubscriptionMetadata(slotId,
+                                    request.mPortIndex,
                                     request.mSubscription,
+                                    request.mSwitchAfterDownload,
                                     request.mForceDeactivateSim,
                                     new IGetDownloadableSubscriptionMetadataCallback.Stub() {
                                         @Override
@@ -1057,9 +1073,8 @@
         for (int slotIndex = 0; slotIndex < slotInfos.length; slotIndex++) {
             // Report Anomaly in case UiccSlotInfo is not.
             if (slotInfos[slotIndex] == null) {
-                AnomalyReporter.reportAnomaly(
-                        UUID.fromString("4195b83d-6cee-4999-a02f-d0b9f7079b9d"),
-                        "EuiccConnector: Found UiccSlotInfo Null object.");
+                Log.i(TAG, "No UiccSlotInfo found for slotIndex: " + slotIndex);
+                return SubscriptionManager.INVALID_SIM_SLOT_INDEX;
             }
             String retrievedCardId = slotInfos[slotIndex] != null
                     ? slotInfos[slotIndex].getCardId() : null;
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccController.java b/src/java/com/android/internal/telephony/euicc/EuiccController.java
index 294299a..a5b95c3 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccController.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccController.java
@@ -59,7 +59,6 @@
 import com.android.internal.telephony.CarrierPrivilegesTracker;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
-import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.euicc.EuiccConnector.OtaStatusChangedCallback;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.uicc.IccUtils;
@@ -386,6 +385,7 @@
 
     void getDownloadableSubscriptionMetadata(int cardId, DownloadableSubscription subscription,
             boolean forceDeactivateSim, String callingPackage, PendingIntent callbackIntent) {
+        Log.d(TAG, " getDownloadableSubscriptionMetadata callingPackage: " + callingPackage);
         if (!callerCanWriteEmbeddedSubscriptions()) {
             throw new SecurityException("Must have WRITE_EMBEDDED_SUBSCRIPTIONS to get metadata");
         }
@@ -393,7 +393,8 @@
         long token = Binder.clearCallingIdentity();
         try {
             mConnector.getDownloadableSubscriptionMetadata(cardId,
-                    subscription, forceDeactivateSim,
+                    TelephonyManager.DEFAULT_PORT_INDEX, subscription,
+                    false /* switchAfterDownload */, forceDeactivateSim,
                     new GetMetadataCommandCallback(
                             token, subscription, callingPackage, callbackIntent));
         } finally {
@@ -602,8 +603,8 @@
             if (!isConsentNeededToResolvePortIndex
                     && canManageSubscriptionOnTargetSim(cardId, callingPackage, true,
                     portIndex)) {
-                mConnector.getDownloadableSubscriptionMetadata(cardId, subscription,
-                    forceDeactivateSim,
+                mConnector.getDownloadableSubscriptionMetadata(cardId, portIndex,
+                        subscription, switchAfterDownload, forceDeactivateSim,
                     new DownloadSubscriptionGetMetadataCommandCallback(token, subscription,
                         switchAfterDownload, callingPackage, forceDeactivateSim,
                         callbackIntent, false /* withUserConsent */, portIndex));
@@ -714,7 +715,8 @@
         Log.d(TAG, " downloadSubscriptionPrivilegedCheckMetadata cardId: " + cardId
                 + " switchAfterDownload: " + switchAfterDownload + " portIndex: " + portIndex
                 + " forceDeactivateSim: " + forceDeactivateSim);
-        mConnector.getDownloadableSubscriptionMetadata(cardId, subscription, forceDeactivateSim,
+        mConnector.getDownloadableSubscriptionMetadata(cardId, portIndex,
+                subscription, switchAfterDownload, forceDeactivateSim,
                 new DownloadSubscriptionGetMetadataCommandCallback(callingToken, subscription,
                         switchAfterDownload, callingPackage, forceDeactivateSim, callbackIntent,
                         true /* withUserConsent */, portIndex));
@@ -863,6 +865,7 @@
 
     void getDefaultDownloadableSubscriptionList(int cardId,
             boolean forceDeactivateSim, String callingPackage, PendingIntent callbackIntent) {
+        Log.d(TAG, " getDefaultDownloadableSubscriptionList callingPackage: " + callingPackage);
         if (!callerCanWriteEmbeddedSubscriptions()) {
             throw new SecurityException(
                     "Must have WRITE_EMBEDDED_SUBSCRIPTIONS to get default list");
@@ -1148,7 +1151,8 @@
      * Returns the resolved portIndex or {@link TelephonyManager#INVALID_PORT_INDEX} if calling
      * cannot manage any active subscription.
      */
-    private int getResolvedPortIndexForDisableSubscription(int cardId, String callingPackage,
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public int getResolvedPortIndexForDisableSubscription(int cardId, String callingPackage,
             boolean callerCanWriteEmbeddedSubscriptions) {
         List<SubscriptionInfo> subInfoList = mSubscriptionManager
                 .getActiveSubscriptionInfoList(/* userVisibleOnly */false);
@@ -1176,7 +1180,8 @@
      * Returns the resolved portIndex or {@link TelephonyManager#INVALID_PORT_INDEX} if no port
      * is available without user consent.
      */
-    private int getResolvedPortIndexForSubscriptionSwitch(int cardId) {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public int getResolvedPortIndexForSubscriptionSwitch(int cardId) {
         int slotIndex = getSlotIndexFromCardId(cardId);
         // Euicc Slot
         UiccSlot slot = UiccController.getInstance().getUiccSlot(slotIndex);
@@ -1586,15 +1591,9 @@
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     public void refreshSubscriptionsAndSendResult(
             PendingIntent callbackIntent, int resultCode, Intent extrasIntent) {
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            SubscriptionManagerService.getInstance().updateEmbeddedSubscriptions(
-                    List.of(mTelephonyManager.getCardIdForDefaultEuicc()),
-                    () -> sendResult(callbackIntent, resultCode, extrasIntent));
-        } else {
-            SubscriptionController.getInstance()
-                    .requestEmbeddedSubscriptionInfoListRefresh(
-                            () -> sendResult(callbackIntent, resultCode, extrasIntent));
-        }
+        SubscriptionManagerService.getInstance().updateEmbeddedSubscriptions(
+                List.of(mTelephonyManager.getCardIdForDefaultEuicc()),
+                () -> sendResult(callbackIntent, resultCode, extrasIntent));
     }
 
     /** Dispatch the given callback intent with the given result code and data. */
@@ -1888,8 +1887,6 @@
             boolean hasActiveEmbeddedSubscription = subInfoList.stream().anyMatch(
                     subInfo -> subInfo.isEmbedded() && subInfo.getCardId() == cardId
                             && (!usePortIndex || subInfo.getPortIndex() == targetPortIndex));
-            Log.d(TAG, "canManageSubscriptionOnTargetSim hasActiveEmbeddedSubscriptions: "
-                    + hasActiveEmbeddedSubscription);
             if (hasActiveEmbeddedSubscription) {
                 // hasActiveEmbeddedSubscription is true if there is an active embedded subscription
                 // on the target port(in case of usePortIndex is true) or if there is an active
@@ -1946,59 +1943,84 @@
 
     @Override
     public boolean isSimPortAvailable(int cardId, int portIndex, String callingPackage) {
-        List<UiccCardInfo> cardInfos;
+        mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
+        // If calling app is targeted for Android U and beyond, check for other conditions
+        // to decide the port availability.
+        boolean shouldCheckConditionsForInactivePort = isCompatChangeEnabled(callingPackage,
+                EuiccManager.INACTIVE_PORT_AVAILABILITY_CHECK);
+        // In the event that this check is coming from ONS, WRITE_EMBEDDED_SUBSCRIPTIONS will be
+        // required for the case where a port is inactive but could trivially be enabled without
+        // requiring user consent.
+        boolean callerCanWriteEmbeddedSubscriptions = callerCanWriteEmbeddedSubscriptions();
         final long token = Binder.clearCallingIdentity();
         try {
-            cardInfos = mTelephonyManager.getUiccCardsInfo();
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-        for (UiccCardInfo info : cardInfos) {
-            if (info == null || info.getCardId() != cardId) {
-                continue;
-            }
-            // Return false in case of non esim or passed port index is greater than
-            // the available ports.
-            if (!info.isEuicc() || (portIndex == TelephonyManager.INVALID_PORT_INDEX)
-                    || portIndex >= info.getPorts().size()) {
-                return false;
-            }
-            for (UiccPortInfo portInfo : info.getPorts()) {
-                if (portInfo == null || portInfo.getPortIndex() != portIndex) {
+            List<UiccCardInfo> cardInfos = mTelephonyManager.getUiccCardsInfo();
+            for (UiccCardInfo info : cardInfos) {
+                if (info == null || info.getCardId() != cardId) {
                     continue;
                 }
-                // Return false if port is not active.
-                if (!portInfo.isActive()) {
+                // Return false in case of non esim or passed port index is greater than
+                // the available ports.
+                if (!info.isEuicc() || (portIndex == TelephonyManager.INVALID_PORT_INDEX)
+                        || portIndex >= info.getPorts().size()) {
                     return false;
                 }
-                // A port is available if it has no profiles enabled on it or calling app has
-                // Carrier privilege over the profile installed on the selected port.
-                if (TextUtils.isEmpty(portInfo.getIccId())) {
-                    return true;
+                for (UiccPortInfo portInfo : info.getPorts()) {
+                    if (portInfo == null || portInfo.getPortIndex() != portIndex) {
+                        continue;
+                    }
+                    if (!portInfo.isActive()) {
+                        // port is inactive, check whether the caller can activate a new profile
+                        // seamlessly. This is possible in below condition:
+                        // 1. Device in DSDS Mode(P+E).
+                        // 2. pSIM slot is active but no active subscription.
+                        // 3. Caller has carrier privileges on any phone or has
+                        // WRITE_EMBEDDED_SUBSCRIPTIONS. The latter covers calls from ONS
+                        // which does not have carrier privileges.
+                        if (!shouldCheckConditionsForInactivePort) {
+                            return false;
+                        }
+                        boolean hasActiveRemovableNonEuiccSlot = getRemovableNonEuiccSlot() != null
+                                && getRemovableNonEuiccSlot().isActive();
+                        boolean hasCarrierPrivileges = mTelephonyManager
+                                .checkCarrierPrivilegesForPackageAnyPhone(callingPackage)
+                                == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
+                        return mTelephonyManager.isMultiSimEnabled()
+                                && hasActiveRemovableNonEuiccSlot
+                                && !isRemovalNonEuiccSlotHasActiveSubscription()
+                                && (hasCarrierPrivileges || callerCanWriteEmbeddedSubscriptions);
+                    }
+                    // A port is available if it has no profiles enabled on it or calling app has
+                    // Carrier privilege over the profile installed on the selected port.
+                    if (TextUtils.isEmpty(portInfo.getIccId())) {
+                        return true;
+                    }
+                    UiccPort uiccPort =
+                            UiccController.getInstance().getUiccPortForSlot(
+                                    info.getPhysicalSlotIndex(), portIndex);
+                    // Some eSim Vendors return boot profile iccid if no profile is installed.
+                    // So in this case if profile is empty, port is available.
+                    if (uiccPort != null
+                            && uiccPort.getUiccProfile() != null
+                            && uiccPort.getUiccProfile().isEmptyProfile()) {
+                        return true;
+                    }
+                    Phone phone = PhoneFactory.getPhone(portInfo.getLogicalSlotIndex());
+                    if (phone == null) {
+                        Log.e(TAG, "Invalid logical slot: " + portInfo.getLogicalSlotIndex());
+                        return false;
+                    }
+                    CarrierPrivilegesTracker cpt = phone.getCarrierPrivilegesTracker();
+                    if (cpt == null) {
+                        Log.e(TAG, "No CarrierPrivilegesTracker");
+                        return false;
+                    }
+                    return (cpt.getCarrierPrivilegeStatusForPackage(callingPackage)
+                            == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
                 }
-                UiccPort uiccPort =
-                        UiccController.getInstance().getUiccPortForSlot(
-                                info.getPhysicalSlotIndex(), portIndex);
-                // Some eSim Vendors return boot profile iccid if no profile is installed.
-                // So in this case if profile is empty, port is available.
-                if (uiccPort != null
-                        && uiccPort.getUiccProfile() != null
-                        && uiccPort.getUiccProfile().isEmptyProfile()) {
-                    return true;
-                }
-                Phone phone = PhoneFactory.getPhone(portInfo.getLogicalSlotIndex());
-                if (phone == null) {
-                    Log.e(TAG, "Invalid logical slot: " + portInfo.getLogicalSlotIndex());
-                    return false;
-                }
-                CarrierPrivilegesTracker cpt = phone.getCarrierPrivilegesTracker();
-                if (cpt == null) {
-                    Log.e(TAG, "No CarrierPrivilegesTracker");
-                    return false;
-                }
-                return (cpt.getCarrierPrivilegeStatusForPackage(callingPackage)
-                        == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
             }
+        } finally {
+            Binder.restoreCallingIdentity(token);
         }
         return false;
     }
diff --git a/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java b/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java
index 0abd4ab..907f158 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java
@@ -24,10 +24,12 @@
 import android.content.IntentFilter;
 import android.os.AsyncResult;
 import android.os.Build;
+import android.os.Looper;
 import android.os.Message;
 import android.os.SystemProperties;
 import android.provider.Telephony.Sms.Intents;
 
+import com.android.ims.ImsManager;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.InboundSmsHandler;
 import com.android.internal.telephony.Phone;
@@ -43,7 +45,7 @@
  */
 public class GsmInboundSmsHandler extends InboundSmsHandler {
 
-    private static BroadcastReceiver sTestBroadcastReceiver;
+    private BroadcastReceiver mTestBroadcastReceiver;
     /** Handler for SMS-PP data download messages to UICC. */
     private final UsimDataDownloadHandler mDataDownloadHandler;
 
@@ -56,18 +58,18 @@
      * Create a new GSM inbound SMS handler.
      */
     private GsmInboundSmsHandler(Context context, SmsStorageMonitor storageMonitor,
-            Phone phone) {
-        super("GsmInboundSmsHandler", context, storageMonitor, phone);
+            Phone phone, Looper looper) {
+        super("GsmInboundSmsHandler", context, storageMonitor, phone, looper);
         phone.mCi.setOnNewGsmSms(getHandler(), EVENT_NEW_SMS, null);
         mDataDownloadHandler = new UsimDataDownloadHandler(phone.mCi, phone.getPhoneId());
         mCellBroadcastServiceManager.enable();
 
         if (TEST_MODE) {
-            if (sTestBroadcastReceiver == null) {
-                sTestBroadcastReceiver = new GsmCbTestBroadcastReceiver();
+            if (mTestBroadcastReceiver == null) {
+                mTestBroadcastReceiver = new GsmCbTestBroadcastReceiver();
                 IntentFilter filter = new IntentFilter();
                 filter.addAction(TEST_ACTION);
-                context.registerReceiver(sTestBroadcastReceiver, filter,
+                context.registerReceiver(mTestBroadcastReceiver, filter,
                         Context.RECEIVER_EXPORTED);
             }
         }
@@ -127,8 +129,9 @@
      * Wait for state machine to enter startup state. We can't send any messages until then.
      */
     public static GsmInboundSmsHandler makeInboundSmsHandler(Context context,
-            SmsStorageMonitor storageMonitor, Phone phone) {
-        GsmInboundSmsHandler handler = new GsmInboundSmsHandler(context, storageMonitor, phone);
+            SmsStorageMonitor storageMonitor, Phone phone, Looper looper) {
+        GsmInboundSmsHandler handler =
+                new GsmInboundSmsHandler(context, storageMonitor, phone, looper);
         handler.start();
         return handler;
     }
@@ -153,7 +156,8 @@
      * or {@link Activity#RESULT_OK} for delayed acknowledgment to SMSC
      */
     @Override
-    protected int dispatchMessageRadioSpecific(SmsMessageBase smsb, @SmsSource int smsSource) {
+    protected int dispatchMessageRadioSpecific(SmsMessageBase smsb, @SmsSource int smsSource,
+            int token) {
         SmsMessage sms = (SmsMessage) smsb;
 
         if (sms.isTypeZero()) {
@@ -177,7 +181,7 @@
         // Send SMS-PP data download messages to UICC. See 3GPP TS 31.111 section 7.1.1.
         if (sms.isUsimDataDownload()) {
             UsimServiceTable ust = mPhone.getUsimServiceTable();
-            return mDataDownloadHandler.handleUsimDataDownload(ust, sms, smsSource);
+            return mDataDownloadHandler.handleUsimDataDownload(ust, sms, smsSource, token);
         }
 
         boolean handled = false;
@@ -268,4 +272,15 @@
                 android.telephony.SmsMessage.FORMAT_3GPP);
         mPhone.getSmsStats().onIncomingSmsVoicemail(false /* is3gpp2 */, smsSource);
     }
+
+    /**
+     * sets ImsManager object.
+     */
+    public boolean setImsManager(ImsManager imsManager) {
+        if (mDataDownloadHandler != null) {
+            mDataDownloadHandler.setImsManager(imsManager);
+            return true;
+        }
+        return false;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java b/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java
index 7f5b607..9de3ee9 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java
@@ -49,6 +49,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.CallForwardInfo;
 import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.CallWaitingController;
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.GsmCdmaPhone;
@@ -1168,11 +1169,25 @@
                 int serviceClass = siToServiceClass(mSia);
 
                 if (isActivate() || isDeactivate()) {
+                    if (serviceClass == SERVICE_CLASS_NONE
+                            || (serviceClass & SERVICE_CLASS_VOICE) == SERVICE_CLASS_VOICE) {
+                        if (mPhone.getTerminalBasedCallWaitingState(true)
+                                != CallWaitingController.TERMINAL_BASED_NOT_SUPPORTED) {
+                            mPhone.setCallWaiting(isActivate(), serviceClass,
+                                    obtainMessage(EVENT_SET_COMPLETE, this));
+                            return;
+                        }
+                    }
                     mPhone.mCi.setCallWaiting(isActivate(), serviceClass,
                             obtainMessage(EVENT_SET_COMPLETE, this));
                 } else if (isInterrogate()) {
-                    mPhone.mCi.queryCallWaiting(serviceClass,
-                            obtainMessage(EVENT_QUERY_COMPLETE, this));
+                    if (mPhone.getTerminalBasedCallWaitingState(true)
+                            != CallWaitingController.TERMINAL_BASED_NOT_SUPPORTED) {
+                        mPhone.getCallWaiting(obtainMessage(EVENT_QUERY_COMPLETE, this));
+                    } else {
+                        mPhone.mCi.queryCallWaiting(serviceClass,
+                                obtainMessage(EVENT_QUERY_COMPLETE, this));
+                    }
                 } else {
                     throw new RuntimeException ("Invalid or Unsupported MMI Code");
                 }
diff --git a/src/java/com/android/internal/telephony/gsm/UsimDataDownloadHandler.java b/src/java/com/android/internal/telephony/gsm/UsimDataDownloadHandler.java
index ed819c1..bae56d1 100644
--- a/src/java/com/android/internal/telephony/gsm/UsimDataDownloadHandler.java
+++ b/src/java/com/android/internal/telephony/gsm/UsimDataDownloadHandler.java
@@ -17,13 +17,19 @@
 package com.android.internal.telephony.gsm;
 
 import android.app.Activity;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
 import android.provider.Telephony.Sms.Intents;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.SmsManager;
+import android.telephony.ims.stub.ImsSmsImplBase;
 
+import com.android.ims.ImsException;
+import com.android.ims.ImsManager;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.InboundSmsHandler;
 import com.android.internal.telephony.PhoneFactory;
@@ -61,10 +67,14 @@
 
     private final CommandsInterface mCi;
     private final int mPhoneId;
+    private ImsManager mImsManager;
+    Resources mResource;
 
     public UsimDataDownloadHandler(CommandsInterface commandsInterface, int phoneId) {
         mCi = commandsInterface;
         mPhoneId = phoneId;
+        mImsManager = null; // will get initialized when ImsManager connection is ready
+        mResource = Resources.getSystem();
     }
 
     /**
@@ -79,7 +89,7 @@
      * @return {@code Activity.RESULT_OK} on success; {@code RESULT_SMS_GENERIC_ERROR} on failure
      */
     int handleUsimDataDownload(UsimServiceTable ust, SmsMessage smsMessage,
-            @InboundSmsHandler.SmsSource int smsSource) {
+            @InboundSmsHandler.SmsSource int smsSource, int token) {
         // If we receive an SMS-PP message before the UsimServiceTable has been loaded,
         // assume that the data download service is not present. This is very unlikely to
         // happen because the IMS connection will not be established until after the ISIM
@@ -87,7 +97,7 @@
         if (ust != null && ust.isAvailable(
                 UsimServiceTable.UsimService.DATA_DL_VIA_SMS_PP)) {
             Rlog.d(TAG, "Received SMS-PP data download, sending to UICC.");
-            return startDataDownload(smsMessage, smsSource);
+            return startDataDownload(smsMessage, smsSource, token);
         } else {
             Rlog.d(TAG, "DATA_DL_VIA_SMS_PP service not available, storing message to UICC.");
             String smsc = IccUtils.bytesToHexString(
@@ -95,7 +105,8 @@
                             smsMessage.getServiceCenterAddress()));
             mCi.writeSmsToSim(SmsManager.STATUS_ON_ICC_UNREAD, smsc,
                     IccUtils.bytesToHexString(smsMessage.getPdu()),
-                    obtainMessage(EVENT_WRITE_SMS_COMPLETE));
+                    obtainMessage(EVENT_WRITE_SMS_COMPLETE,
+                            new int[]{ smsSource, smsMessage.mMessageRef, token }));
             addUsimDataDownloadToMetrics(false, smsSource);
             return Activity.RESULT_OK;  // acknowledge after response from write to USIM
         }
@@ -111,9 +122,9 @@
      * @return {@code Activity.RESULT_OK} on success; {@code RESULT_SMS_GENERIC_ERROR} on failure
      */
     public int startDataDownload(SmsMessage smsMessage,
-            @InboundSmsHandler.SmsSource int smsSource) {
+            @InboundSmsHandler.SmsSource int smsSource, int token) {
         if (sendMessage(obtainMessage(EVENT_START_DATA_DOWNLOAD,
-                smsSource, 0 /* unused */, smsMessage))) {
+                smsSource, token, smsMessage))) {
             return Activity.RESULT_OK;  // we will send SMS ACK/ERROR based on UICC response
         } else {
             Rlog.e(TAG, "startDataDownload failed to send message to start data download.");
@@ -122,7 +133,7 @@
     }
 
     private void handleDataDownload(SmsMessage smsMessage,
-            @InboundSmsHandler.SmsSource int smsSource) {
+            @InboundSmsHandler.SmsSource int smsSource, int token) {
         int dcs = smsMessage.getDataCodingScheme();
         int pid = smsMessage.getProtocolIdentifier();
         byte[] pdu = smsMessage.getPdu();           // includes SC address
@@ -139,6 +150,7 @@
 
         byte[] envelope = new byte[totalLength];
         int index = 0;
+        Rlog.d(TAG, "smsSource: " + smsSource + "Token: " + token);
 
         // SMS-PP download tag and length (assumed to be < 256 bytes).
         envelope[index++] = (byte) BER_SMS_PP_DOWNLOAD_TAG;
@@ -173,14 +185,16 @@
         // Verify that we calculated the payload size correctly.
         if (index != envelope.length) {
             Rlog.e(TAG, "startDataDownload() calculated incorrect envelope length, aborting.");
-            acknowledgeSmsWithError(CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR);
+            acknowledgeSmsWithError(CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR,
+                    smsSource, token, smsMessage.mMessageRef);
             addUsimDataDownloadToMetrics(false, smsSource);
             return;
         }
 
         String encodedEnvelope = IccUtils.bytesToHexString(envelope);
         mCi.sendEnvelopeWithStatus(encodedEnvelope, obtainMessage(
-                EVENT_SEND_ENVELOPE_RESPONSE, new int[]{ dcs, pid }));
+                EVENT_SEND_ENVELOPE_RESPONSE, new int[]{ dcs, pid, smsSource,
+                    smsMessage.mMessageRef, token }));
 
         addUsimDataDownloadToMetrics(true, smsSource);
     }
@@ -211,7 +225,8 @@
      * @param response UICC response encoded as hexadecimal digits. First two bytes are the
      *  UICC SW1 and SW2 status bytes.
      */
-    private void sendSmsAckForEnvelopeResponse(IccIoResult response, int dcs, int pid) {
+    private void sendSmsAckForEnvelopeResponse(IccIoResult response, int dcs, int pid,
+            int smsSource, int token, int messageRef) {
         int sw1 = response.sw1;
         int sw2 = response.sw2;
 
@@ -221,7 +236,8 @@
             success = true;
         } else if (sw1 == 0x93 && sw2 == 0x00) {
             Rlog.e(TAG, "USIM data download failed: Toolkit busy");
-            acknowledgeSmsWithError(CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_APP_TOOLKIT_BUSY);
+            acknowledgeSmsWithError(CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_APP_TOOLKIT_BUSY,
+                    smsSource, token, messageRef);
             return;
         } else if (sw1 == 0x62 || sw1 == 0x63) {
             Rlog.e(TAG, "USIM data download failed: " + response.toString());
@@ -234,10 +250,11 @@
         byte[] responseBytes = response.payload;
         if (responseBytes == null || responseBytes.length == 0) {
             if (success) {
-                mCi.acknowledgeLastIncomingGsmSms(true, 0, null);
+                acknowledgeSmsWithSuccess(0, smsSource, token, messageRef);
             } else {
                 acknowledgeSmsWithError(
-                        CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR);
+                        CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR, smsSource,
+                        token, messageRef);
             }
             return;
         }
@@ -268,12 +285,32 @@
 
         System.arraycopy(responseBytes, 0, smsAckPdu, index, responseBytes.length);
 
-        mCi.acknowledgeIncomingGsmSmsWithPdu(success,
-                IccUtils.bytesToHexString(smsAckPdu), null);
+        if (smsSource == InboundSmsHandler.SOURCE_INJECTED_FROM_IMS && ackViaIms()) {
+            acknowledgeImsSms(token, messageRef, true, smsAckPdu);
+        } else {
+            mCi.acknowledgeIncomingGsmSmsWithPdu(success,
+                    IccUtils.bytesToHexString(smsAckPdu), null);
+        }
     }
 
-    private void acknowledgeSmsWithError(int cause) {
-        mCi.acknowledgeLastIncomingGsmSms(false, cause, null);
+    private void acknowledgeSmsWithSuccess(int cause, int smsSource, int token, int messageRef) {
+        Rlog.d(TAG, "acknowledgeSmsWithSuccess- cause: " + cause + " smsSource: " + smsSource
+                + " token: " + token + " messageRef: " + messageRef);
+        if (smsSource == InboundSmsHandler.SOURCE_INJECTED_FROM_IMS && ackViaIms()) {
+            acknowledgeImsSms(token, messageRef, true, null);
+        } else {
+            mCi.acknowledgeLastIncomingGsmSms(true, cause, null);
+        }
+    }
+
+    private void acknowledgeSmsWithError(int cause, int smsSource, int token, int messageRef) {
+        Rlog.d(TAG, "acknowledgeSmsWithError- cause: " + cause + " smsSource: " + smsSource
+                + " token: " + token + " messageRef: " + messageRef);
+        if (smsSource == InboundSmsHandler.SOURCE_INJECTED_FROM_IMS && ackViaIms()) {
+            acknowledgeImsSms(token, messageRef, false, null);
+        } else {
+            mCi.acknowledgeLastIncomingGsmSms(false, cause, null);
+        }
     }
 
     /**
@@ -300,6 +337,45 @@
     }
 
     /**
+     * Route resposes via ImsManager based on config
+     */
+    private boolean ackViaIms() {
+        boolean isViaIms;
+
+        try {
+            isViaIms = mResource.getBoolean(
+                    com.android.internal.R.bool.config_smppsim_response_via_ims);
+        } catch (NotFoundException e) {
+            isViaIms = false;
+        }
+
+        Rlog.d(TAG, "ackViaIms : " + isViaIms);
+        return isViaIms;
+    }
+
+    /**
+     * Acknowledges IMS SMS and delivers the result based on the envelope or SIM saving respose
+     * received from SIM for SMS-PP Data.
+     */
+    private void acknowledgeImsSms(int token, int messageRef, boolean success, byte[] pdu) {
+        int result = success ? ImsSmsImplBase.DELIVER_STATUS_OK :
+                    ImsSmsImplBase.DELIVER_STATUS_ERROR_GENERIC;
+        Rlog.d(TAG, "sending result via acknowledgeImsSms: " + result + " token: " + token);
+
+        try {
+            if (mImsManager != null) {
+                if (pdu != null && pdu.length > 0) {
+                    mImsManager.acknowledgeSms(token, messageRef, result, pdu);
+                } else {
+                    mImsManager.acknowledgeSms(token, messageRef, result);
+                }
+            }
+        } catch (ImsException e) {
+            Rlog.e(TAG, "Failed to acknowledgeSms(). Error: " + e.getMessage());
+        }
+    }
+
+    /**
      * Handle UICC envelope response and send SMS acknowledgement.
      *
      * @param msg the message to handle
@@ -307,35 +383,60 @@
     @Override
     public void handleMessage(Message msg) {
         AsyncResult ar;
+        int smsSource = InboundSmsHandler.SOURCE_INJECTED_FROM_UNKNOWN;
+        int token = 0;
+        int messageRef = 0;
+        int[] responseInfo;
 
         switch (msg.what) {
             case EVENT_START_DATA_DOWNLOAD:
-                handleDataDownload((SmsMessage) msg.obj, msg.arg1 /* smsSource */);
+                Rlog.d(TAG, "EVENT_START_DATA_DOWNLOAD");
+                handleDataDownload((SmsMessage) msg.obj, msg.arg1 /* smsSource */,
+                        msg.arg2 /* token */);
                 break;
 
             case EVENT_SEND_ENVELOPE_RESPONSE:
                 ar = (AsyncResult) msg.obj;
 
+                responseInfo = (int[]) ar.userObj;
+                smsSource = responseInfo[2];
+                messageRef = responseInfo[3];
+                token = responseInfo[4];
+
+                Rlog.d(TAG, "Received EVENT_SEND_ENVELOPE_RESPONSE from source : " + smsSource);
+
                 if (ar.exception != null) {
                     Rlog.e(TAG, "UICC Send Envelope failure, exception: " + ar.exception);
+
                     acknowledgeSmsWithError(
-                            CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR);
+                            CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR,
+                            smsSource, token, messageRef);
                     return;
                 }
 
-                int[] dcsPid = (int[]) ar.userObj;
-                sendSmsAckForEnvelopeResponse((IccIoResult) ar.result, dcsPid[0], dcsPid[1]);
+                Rlog.d(TAG, "Successful in sending envelope response");
+                sendSmsAckForEnvelopeResponse((IccIoResult) ar.result, responseInfo[0],
+                            responseInfo[1], smsSource, token, messageRef);
                 break;
 
             case EVENT_WRITE_SMS_COMPLETE:
                 ar = (AsyncResult) msg.obj;
+
+                responseInfo = (int[]) ar.userObj;
+                smsSource = responseInfo[0];
+                messageRef = responseInfo[1];
+                token = responseInfo[2];
+
+                Rlog.d(TAG, "Received EVENT_WRITE_SMS_COMPLETE from source : " + smsSource);
+
                 if (ar.exception == null) {
                     Rlog.d(TAG, "Successfully wrote SMS-PP message to UICC");
-                    mCi.acknowledgeLastIncomingGsmSms(true, 0, null);
+                    acknowledgeSmsWithSuccess(0, smsSource, token, messageRef);
                 } else {
                     Rlog.d(TAG, "Failed to write SMS-PP message to UICC", ar.exception);
-                    mCi.acknowledgeLastIncomingGsmSms(false,
-                            CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR, null);
+                    acknowledgeSmsWithError(
+                            CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR,
+                            smsSource, token, messageRef);
                 }
                 break;
 
@@ -343,4 +444,23 @@
                 Rlog.e(TAG, "Ignoring unexpected message, what=" + msg.what);
         }
     }
+
+    /**
+     * Called when ImsManager connection is ready. ImsManager object will be used to send ACK to IMS
+     * which doesn't use RIL interface.
+     * @param imsManager object
+     */
+    public void setImsManager(ImsManager imsManager) {
+        mImsManager = imsManager;
+    }
+
+    /**
+     * Called to set mocked object of type Resources during unit testing of this file.
+     * @param resource object
+     */
+    @VisibleForTesting
+    public void setResourcesForTest(Resources resource) {
+        mResource = resource;
+        Rlog.d(TAG, "setResourcesForTest");
+    }
 }
diff --git a/src/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java b/src/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java
old mode 100755
new mode 100644
index e594ab6..48be16c
--- a/src/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java
+++ b/src/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java
@@ -32,6 +32,7 @@
 import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
+import java.util.Locale;
 
 /**
  * This class implements reading and parsing USIM records.
@@ -233,7 +234,7 @@
 
             int emailEfid = email.getEfid();
             log("EF_EMAIL exists in PBR. efid = 0x" +
-                    Integer.toHexString(emailEfid).toUpperCase());
+                    Integer.toHexString(emailEfid).toUpperCase(Locale.ROOT));
 
             /**
              * Make sure this EF_EMAIL was never read earlier. Sometimes two PBR record points
@@ -348,7 +349,7 @@
                 emailList = new ArrayList<String>();
             }
             log("Adding email #" + i + " list to index 0x" +
-                    Integer.toHexString(index).toUpperCase());
+                    Integer.toHexString(index).toUpperCase(Locale.ROOT));
             emailList.add(email);
             mEmailsForAdnRec.put(index, emailList);
         }
@@ -402,7 +403,7 @@
                 }
                 emailList.add(email);
                 log("Adding email list to index 0x" +
-                        Integer.toHexString(index).toUpperCase());
+                        Integer.toHexString(index).toUpperCase(Locale.ROOT));
                 mEmailsForAdnRec.put(index, emailList);
             }
         }
@@ -446,8 +447,9 @@
             System.arraycopy(emailList.toArray(), 0, emails, 0, emailList.size());
             rec.setEmails(emails);
             log("Adding email list to ADN (0x" +
-                    Integer.toHexString(mPhoneBookRecords.get(i).getEfid()).toUpperCase() +
-                    ") record #" + mPhoneBookRecords.get(i).getRecId());
+                    Integer.toHexString(mPhoneBookRecords.get(i).getEfid())
+                            .toUpperCase(Locale.ROOT) + ") record #"
+                    + mPhoneBookRecords.get(i).getRecId());
             mPhoneBookRecords.set(i, rec);
         }
     }
diff --git a/src/java/com/android/internal/telephony/ims/ImsEnablementTracker.java b/src/java/com/android/internal/telephony/ims/ImsEnablementTracker.java
new file mode 100644
index 0000000..e54561f
--- /dev/null
+++ b/src/java/com/android/internal/telephony/ims/ImsEnablementTracker.java
@@ -0,0 +1,939 @@
+/*
+ * Copyright (C) 2022 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.ims;
+
+import android.content.ComponentName;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.telephony.SubscriptionManager;
+import android.telephony.ims.aidl.IImsServiceController;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IState;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class will abstract away all the new enablement logic and take the reset/enable/disable
+ * IMS commands as inputs.
+ * The IMS commands will call enableIms, disableIms or resetIms to match the enablement state only
+ * when it changes.
+ */
+public class ImsEnablementTracker {
+    private static final String LOG_TAG = "ImsEnablementTracker";
+    private static final long REQUEST_THROTTLE_TIME_MS = 3 * 1000L; // 3 seconds
+
+    private static final int COMMAND_NONE_MSG = 0;
+    // Indicate that the enableIms command has been received.
+    @VisibleForTesting
+    public static final int COMMAND_ENABLE_MSG = 1;
+    // Indicate that the disableIms command has been received.
+    @VisibleForTesting
+    public static final int COMMAND_DISABLE_MSG = 2;
+    // Indicate that the resetIms command has been received.
+    private static final int COMMAND_RESET_MSG = 3;
+    // Indicate that the internal enable message with delay has been received.
+    private static final int COMMAND_ENABLING_DONE = 4;
+    // Indicate that the internal disable message with delay has been received.
+    private static final int COMMAND_DISABLING_DONE = 5;
+    // Indicate that the internal reset message with delay has been received.
+    @VisibleForTesting
+    public static final int COMMAND_RESETTING_DONE = 6;
+    // The ImsServiceController binder is connected.
+    private static final int COMMAND_CONNECTED_MSG = 7;
+    // The ImsServiceController binder is disconnected.
+    private static final int COMMAND_DISCONNECTED_MSG = 8;
+    // The subId is changed to INVALID_SUBSCRIPTION_ID.
+    private static final int COMMAND_INVALID_SUBID_MSG = 9;
+    // Indicate that the internal post reset message with delay has been received.
+    @VisibleForTesting
+    public static final int COMMAND_POST_RESETTING_DONE = 10;
+
+    private static final Map<Integer, String> EVENT_DESCRIPTION = new HashMap<>();
+    static {
+        EVENT_DESCRIPTION.put(COMMAND_NONE_MSG, "COMMAND_NONE_MSG");
+        EVENT_DESCRIPTION.put(COMMAND_ENABLE_MSG, "COMMAND_ENABLE_MSG");
+        EVENT_DESCRIPTION.put(COMMAND_DISABLE_MSG, "COMMAND_DISABLE_MSG");
+        EVENT_DESCRIPTION.put(COMMAND_RESET_MSG, "COMMAND_RESET_MSG");
+        EVENT_DESCRIPTION.put(COMMAND_ENABLING_DONE, "COMMAND_ENABLING_DONE");
+        EVENT_DESCRIPTION.put(COMMAND_DISABLING_DONE, "COMMAND_DISABLING_DONE");
+        EVENT_DESCRIPTION.put(COMMAND_RESETTING_DONE, "COMMAND_RESETTING_DONE");
+        EVENT_DESCRIPTION.put(COMMAND_CONNECTED_MSG, "COMMAND_CONNECTED_MSG");
+        EVENT_DESCRIPTION.put(COMMAND_DISCONNECTED_MSG, "COMMAND_DISCONNECTED_MSG");
+        EVENT_DESCRIPTION.put(COMMAND_INVALID_SUBID_MSG, "COMMAND_INVALID_SUBID_MSG");
+    }
+
+    @VisibleForTesting
+    protected static final int STATE_IMS_DISCONNECTED = 0;
+    @VisibleForTesting
+    protected static final int STATE_IMS_DEFAULT = 1;
+    @VisibleForTesting
+    protected static final int STATE_IMS_ENABLED = 2;
+    @VisibleForTesting
+    protected static final int STATE_IMS_DISABLING = 3;
+    @VisibleForTesting
+    protected static final int STATE_IMS_DISABLED = 4;
+    @VisibleForTesting
+    protected static final int STATE_IMS_ENABLING = 5;
+    @VisibleForTesting
+    protected static final int STATE_IMS_RESETTING = 6;
+
+    @VisibleForTesting
+    protected static final int STATE_IMS_POSTRESETTING = 7;
+
+    protected final Object mLock = new Object();
+    private IImsServiceController mIImsServiceController;
+    private long mLastImsOperationTimeMs = 0L;
+    private final ComponentName mComponentName;
+    private final SparseArray<ImsEnablementTrackerStateMachine> mStateMachines;
+
+    private final Looper mLooper;
+    private final int mState;
+
+    /**
+     * Provides Ims Enablement Tracker State Machine responsible for ims enable/disable/reset
+     * command interactions with Ims service controller binder.
+     * The enable/disable/reset ims commands have a time interval of at least
+     * {@link ImsEnablementTracker#REQUEST_THROTTLE_TIME_MS} second between
+     * processing each command.
+     * For example, the enableIms command is received and the binder's enableIms is called.
+     * After that, if the disableIms command is received, the binder's disableIms will be
+     * called after {@link ImsEnablementTracker#REQUEST_THROTTLE_TIME_MS} second.
+     * A time of {@link ImsEnablementTracker#REQUEST_THROTTLE_TIME_MS} will be used
+     * {@link Handler#sendMessageDelayed(Message, long)},
+     * and the enabled, disabled and reset states are responsible for waiting for
+     * that delay message.
+     */
+    class ImsEnablementTrackerStateMachine extends StateMachine {
+        /**
+         * The initial state of this class and waiting for an ims commands.
+         */
+        @VisibleForTesting
+        public final Default mDefault;
+
+        /**
+         * Indicates that {@link IImsServiceController#enableIms(int, int)} has been called and
+         * waiting for an ims commands.
+         * Common transitions are to
+         * {@link #mDisabling} state when the disable command is received
+         * or {@link #mResetting} state when the reset command is received
+         * or {@link #mDisconnected} if the binder is disconnected.
+         */
+        @VisibleForTesting
+        public final Enabled mEnabled;
+
+        /**
+         * Indicates that the state waiting for the throttle time to elapse before calling
+         * {@link IImsServiceController#disableIms(int, int)}.
+         * Common transitions are to
+         * {@link #mEnabled} when the enable command is received.
+         * or {@link #mResetting} when the reset command is received.
+         * or {@link #mDisabled} the previous binder API call has passed
+         * {@link ImsEnablementTracker#REQUEST_THROTTLE_TIME_MS} second, and if
+         * {@link IImsServiceController#disableIms(int, int)} called.
+         * or {@link #mDisabling} received a disableIms message and the previous binder API call
+         * has not passed {@link ImsEnablementTracker#REQUEST_THROTTLE_TIME_MS} second.
+         * Then send a disableIms message with delay.
+         * or {@link #mDisconnected} if the binder is disconnected.
+         */
+        @VisibleForTesting
+        public final Disabling mDisabling;
+
+        /**
+         * Indicates that {@link IImsServiceController#disableIms(int, int)} has been called and
+         * waiting for an ims commands.
+         * Common transitions are to
+         * {@link #mEnabling} state when the enable command is received.
+         * or {@link #mDisconnected} if the binder is disconnected.
+         */
+        @VisibleForTesting
+        public final Disabled mDisabled;
+
+        /**
+         * Indicates that the state waiting for the throttle time to elapse before calling
+         * {@link IImsServiceController#enableIms(int, int)}.
+         * Common transitions are to
+         * {@link #mEnabled} the previous binder API call has passed
+         * {@link ImsEnablementTracker#REQUEST_THROTTLE_TIME_MS} second, and
+         * {@link IImsServiceController#enableIms(int, int)} called.
+         * or {@link #mDisabled} when the disable command is received.
+         * or {@link #mEnabling} received an enableIms message and the previous binder API call
+         * has not passed {@link ImsEnablementTracker#REQUEST_THROTTLE_TIME_MS} second.
+         * Then send an enableIms message with delay.
+         * or {@link #mDisconnected} if the binder is disconnected.
+         */
+        @VisibleForTesting
+        public final Enabling mEnabling;
+
+        /**
+         * Indicates that the state waiting for the throttle time to elapse before calling
+         * {@link IImsServiceController#resetIms(int, int)}.
+         * Common transitions are to
+         * {@link #mPostResetting} state to call either enableIms or disableIms after calling
+         * {@link IImsServiceController#resetIms(int, int)}
+         * or {@link #mDisconnected} if the binder is disconnected.
+         */
+        @VisibleForTesting
+        public final Resetting mResetting;
+
+        /**
+         * Indicates that the state waiting after resetIms for the throttle time to elapse before
+         * calling {@link IImsServiceController#enableIms(int, int)} or
+         * {@link IImsServiceController#disableIms(int, int)}.
+         * Common transitions are to
+         * {@link #mEnabled} state when the disable command is received,
+         * {@link #mDisabled} state when the enable command is received after calling
+         * {@link IImsServiceController#enableIms(int, int)},
+         * {@link IImsServiceController#disableIms(int, int)}
+         * or {@link #mDisconnected} if the binder is disconnected.
+         */
+        public final PostResetting mPostResetting;
+
+        /**
+         * Indicates that {@link IImsServiceController} has not been set.
+         * Common transition is to
+         * {@link #mDefault} state when the binder is set.
+         * or {@link #mDisabling} If the disable command is received while the binder is
+         * disconnected
+         * or {@link #mEnabling} If the enable command is received while the binder is
+         * disconnected
+         */
+
+        private final Disconnected mDisconnected;
+        private int mSlotId;
+        private int mSubId;
+
+        private final int mPhoneId;
+
+        private IState mPreviousState;
+
+        private int mLastMsg = COMMAND_NONE_MSG;
+
+        ImsEnablementTrackerStateMachine(String name, Looper looper, int state, int slotId) {
+            super(name, looper);
+            mPhoneId = slotId;
+            mDefault = new Default();
+            mEnabled = new Enabled();
+            mDisabling = new Disabling();
+            mDisabled = new Disabled();
+            mEnabling = new Enabling();
+            mResetting = new Resetting();
+            mDisconnected = new Disconnected();
+            mPostResetting = new PostResetting();
+
+            addState(mDefault);
+            addState(mEnabled);
+            addState(mDisabling);
+            addState(mDisabled);
+            addState(mEnabling);
+            addState(mResetting);
+            addState(mDisconnected);
+            addState(mPostResetting);
+
+            setInitialState(getState(state));
+            mPreviousState = getState(state);
+        }
+
+        public void clearAllMessage() {
+            Log.d(LOG_TAG, "clearAllMessage");
+            removeMessages(COMMAND_ENABLE_MSG);
+            removeMessages(COMMAND_DISABLE_MSG);
+            removeMessages(COMMAND_RESET_MSG);
+            removeMessages(COMMAND_ENABLING_DONE);
+            removeMessages(COMMAND_DISABLING_DONE);
+            removeMessages(COMMAND_RESETTING_DONE);
+            removeMessages(COMMAND_POST_RESETTING_DONE);
+        }
+
+        public void serviceBinderConnected() {
+            clearAllMessage();
+            sendMessage(COMMAND_CONNECTED_MSG);
+        }
+
+        public void serviceBinderDisconnected() {
+            clearAllMessage();
+            sendMessage(COMMAND_DISCONNECTED_MSG);
+        }
+
+        @VisibleForTesting
+        public boolean isState(int state) {
+            State expect = null;
+            switch (state) {
+                case Default.STATE_NO:
+                    expect = mDefault;
+                    break;
+                case Enabled.STATE_NO:
+                    expect = mEnabled;
+                    break;
+                case Disabling.STATE_NO:
+                    expect = mDisabling;
+                    break;
+                case Disabled.STATE_NO:
+                    expect = mDisabled;
+                    break;
+                case Enabling.STATE_NO:
+                    expect = mEnabling;
+                    break;
+                case Resetting.STATE_NO:
+                    expect = mResetting;
+                    break;
+                case Disconnected.STATE_NO:
+                    expect = mDisconnected;
+                    break;
+                case PostResetting.STATE_NO:
+                    expect = mPostResetting;
+                    break;
+                default:
+                    break;
+            }
+            return (getCurrentState() == expect) ? true : false;
+        }
+
+        private State getState(int state) {
+            switch (state) {
+                case ImsEnablementTracker.STATE_IMS_ENABLED:
+                    return mEnabled;
+                case ImsEnablementTracker.STATE_IMS_DISABLING:
+                    return mDisabling;
+                case ImsEnablementTracker.STATE_IMS_DISABLED:
+                    return mDisabled;
+                case ImsEnablementTracker.STATE_IMS_ENABLING:
+                    return mEnabling;
+                case ImsEnablementTracker.STATE_IMS_RESETTING:
+                    return mResetting;
+                case ImsEnablementTracker.STATE_IMS_DISCONNECTED:
+                    return mDisconnected;
+                case ImsEnablementTracker.STATE_IMS_POSTRESETTING:
+                    return mPostResetting;
+                default:
+                    return mDefault;
+            }
+        }
+
+        private void handleInvalidSubIdMessage() {
+            clearAllMessage();
+            transitionState(mDefault);
+        }
+
+        private void transitionState(State state) {
+            mPreviousState = getCurrentState();
+            transitionTo(state);
+        }
+
+        class Default extends State {
+            private static final int STATE_NO = STATE_IMS_DEFAULT;
+
+            @Override
+            public void enter() {
+                Log.d(LOG_TAG, "[" + mPhoneId + "]Default state:enter");
+                mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                Log.d(LOG_TAG, "[" + mPhoneId + "]Default state:processMessage. msg.what="
+                        + EVENT_DESCRIPTION.get(message.what) + ",component:" + mComponentName);
+
+                switch (message.what) {
+                    // When enableIms() is called, enableIms of binder is call and the state
+                    // change to the enabled state.
+                    case COMMAND_ENABLE_MSG:
+                        sendEnableIms(message.arg1, message.arg2);
+                        transitionState(mEnabled);
+                        return HANDLED;
+                    // When disableIms() is called, disableIms of binder is call and the state
+                    // change to the disabled state.
+                    case COMMAND_DISABLE_MSG:
+                        sendDisableIms(message.arg1, message.arg2);
+                        transitionState(mDisabled);
+                        return HANDLED;
+                    // When resetIms() is called, change to the resetting state to call enableIms
+                    // after calling resetIms of binder.
+                    case COMMAND_RESET_MSG:
+                        mSlotId = message.arg1;
+                        mSubId = message.arg2;
+                        transitionState(mResetting);
+                        return HANDLED;
+                    case COMMAND_DISCONNECTED_MSG:
+                        transitionState(mDisconnected);
+                        return HANDLED;
+                    default:
+                        return NOT_HANDLED;
+                }
+            }
+        }
+
+        class Enabled extends State {
+            private static final int STATE_NO = STATE_IMS_ENABLED;
+
+            @Override
+            public void enter() {
+                Log.d(LOG_TAG, "[" + mPhoneId + "]Enabled state:enter");
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                Log.d(LOG_TAG, "[" + mPhoneId + "]Enabled state:processMessage. msg.what="
+                        + EVENT_DESCRIPTION.get(message.what) + ",component:" + mComponentName);
+
+                switch (message.what) {
+                    // the disableIms() is called.
+                    case COMMAND_DISABLE_MSG:
+                        mSlotId = message.arg1;
+                        mSubId = message.arg2;
+                        transitionState(mDisabling);
+                        return HANDLED;
+                    // the resetIms() is called.
+                    case COMMAND_RESET_MSG:
+                        mSlotId = message.arg1;
+                        mSubId = message.arg2;
+                        transitionState(mResetting);
+                        return HANDLED;
+                    case COMMAND_DISCONNECTED_MSG:
+                        transitionState(mDisconnected);
+                        return HANDLED;
+                    case COMMAND_INVALID_SUBID_MSG:
+                        handleInvalidSubIdMessage();
+                        return HANDLED;
+                    default:
+                        return NOT_HANDLED;
+                }
+            }
+        }
+
+        class Disabling extends State {
+            private static final int STATE_NO = STATE_IMS_DISABLING;
+
+            @Override
+            public void enter() {
+                Log.d(LOG_TAG, "[" + mPhoneId + "]Disabling state:enter");
+                sendMessageDelayed(COMMAND_DISABLING_DONE, mSlotId, mSubId,
+                        getRemainThrottleTime());
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                Log.d(LOG_TAG, "[" + mPhoneId + "]Disabling state:processMessage. msg.what="
+                        + EVENT_DESCRIPTION.get(message.what) + ",component:" + mComponentName);
+
+                switch (message.what) {
+                    case COMMAND_ENABLE_MSG:
+                        mSlotId = message.arg1;
+                        mSubId = message.arg2;
+                        clearAllMessage();
+                        if (mPreviousState == mResetting) {
+                            // if we are moving from Resetting -> Disabling and receive
+                            // the COMMAND_ENABLE_MSG, we need to send the enableIms command,
+                            // so move to Enabling state.
+                            transitionState(mEnabling);
+                        } else {
+                            // When moving from Enabled -> Disabling and we receive an ENABLE_MSG,
+                            // we can move straight back to Enabled state because we have not sent
+                            // the disableIms command to IMS yet.
+                            transitionState(mEnabled);
+                        }
+                        return HANDLED;
+                    case COMMAND_DISABLING_DONE:
+                        // If the disable command is received before disableIms is processed,
+                        // it will be ignored because the disable command processing is in progress.
+                        removeMessages(COMMAND_DISABLE_MSG);
+                        sendDisableIms(message.arg1, message.arg2);
+                        transitionState(mDisabled);
+                        return HANDLED;
+                    case COMMAND_RESET_MSG:
+                        mSlotId = message.arg1;
+                        mSubId = message.arg2;
+                        clearAllMessage();
+                        transitionState(mResetting);
+                        return HANDLED;
+                    case COMMAND_DISCONNECTED_MSG:
+                        transitionState(mDisconnected);
+                        return HANDLED;
+                    case COMMAND_INVALID_SUBID_MSG:
+                        handleInvalidSubIdMessage();
+                        return HANDLED;
+                    default:
+                        return NOT_HANDLED;
+                }
+            }
+        }
+
+        class Disabled extends State {
+            private static final int STATE_NO = STATE_IMS_DISABLED;
+
+            @Override
+            public void enter() {
+                Log.d(LOG_TAG, "[" + mPhoneId + "]Disabled state:enter");
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                Log.d(LOG_TAG, "[" + mPhoneId + "]Disabled state:processMessage. msg.what="
+                        + EVENT_DESCRIPTION.get(message.what) + ",component:" + mComponentName);
+
+                switch (message.what) {
+                    case COMMAND_ENABLE_MSG:
+                        mSlotId = message.arg1;
+                        mSubId = message.arg2;
+                        transitionState(mEnabling);
+                        return HANDLED;
+                    case COMMAND_RESET_MSG:
+                        mSlotId = message.arg1;
+                        mSubId = message.arg2;
+                        transitionState(mResetting);
+                        return HANDLED;
+                    case COMMAND_DISCONNECTED_MSG:
+                        transitionState(mDisconnected);
+                        return HANDLED;
+                    case COMMAND_INVALID_SUBID_MSG:
+                        handleInvalidSubIdMessage();
+                        return HANDLED;
+                    default:
+                        return NOT_HANDLED;
+                }
+            }
+        }
+
+        class Enabling extends State {
+            private static final int STATE_NO = STATE_IMS_ENABLING;
+
+            @Override
+            public void enter() {
+                Log.d(LOG_TAG, "[" + mPhoneId + "]Enabling state:enter");
+                sendMessageDelayed(COMMAND_ENABLING_DONE, mSlotId, mSubId, getRemainThrottleTime());
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                Log.d(LOG_TAG, "[" + mPhoneId + "]Enabling state:processMessage. msg.what="
+                        + EVENT_DESCRIPTION.get(message.what) + ",component:" + mComponentName);
+
+                switch (message.what) {
+                    // Enabling state comes from Resetting and disableIms() is called.
+                    // In this case disableIms() of binder should be called.
+                    // When enabling state comes from disabled, just change state to the disabled.
+                    case COMMAND_DISABLE_MSG:
+                        mSlotId = message.arg1;
+                        mSubId = message.arg2;
+                        clearAllMessage();
+                        if (mPreviousState == mResetting) {
+                            transitionState(mDisabling);
+                        } else {
+                            transitionState(mDisabled);
+                        }
+                        return HANDLED;
+                    case COMMAND_RESET_MSG:
+                        mSlotId = message.arg1;
+                        mSubId = message.arg2;
+                        transitionState(mResetting);
+                        return HANDLED;
+                    case COMMAND_ENABLING_DONE:
+                        // If the enable command is received before enableIms is processed,
+                        // it will be ignored because the enable command processing is in progress.
+                        removeMessages(COMMAND_ENABLE_MSG);
+                        sendEnableIms(message.arg1, message.arg2);
+                        transitionState(mEnabled);
+                        return HANDLED;
+                    case COMMAND_DISCONNECTED_MSG:
+                        transitionState(mDisconnected);
+                        return HANDLED;
+                    case COMMAND_INVALID_SUBID_MSG:
+                        handleInvalidSubIdMessage();
+                        return HANDLED;
+                    default:
+                        return NOT_HANDLED;
+                }
+            }
+        }
+
+        class Resetting extends State {
+            private static final int STATE_NO = STATE_IMS_RESETTING;
+
+            @Override
+            public void enter() {
+                Log.d(LOG_TAG, "[" + mPhoneId + "]Resetting state:enter");
+                sendMessageDelayed(COMMAND_RESETTING_DONE, mSlotId, mSubId,
+                        getRemainThrottleTime());
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                Log.d(LOG_TAG, "[" + mPhoneId + "]Resetting state:processMessage. msg.what="
+                        + EVENT_DESCRIPTION.get(message.what) + ",component:" + mComponentName);
+
+                switch (message.what) {
+                    case COMMAND_DISABLE_MSG:
+                        mLastMsg = COMMAND_DISABLE_MSG;
+                        return HANDLED;
+                    case COMMAND_ENABLE_MSG:
+                        mLastMsg = COMMAND_ENABLE_MSG;
+                        return HANDLED;
+                    case COMMAND_RESETTING_DONE:
+                        mSlotId = message.arg1;
+                        mSubId = message.arg2;
+                        // If the reset command is received before disableIms is processed,
+                        // it will be ignored because the reset command processing is in progress.
+                        removeMessages(COMMAND_RESET_MSG);
+                        sendResetIms(mSlotId, mSubId);
+                        transitionState(mPostResetting);
+                        return HANDLED;
+                    case COMMAND_DISCONNECTED_MSG:
+                        transitionState(mDisconnected);
+                        return HANDLED;
+                    case COMMAND_INVALID_SUBID_MSG:
+                        handleInvalidSubIdMessage();
+                        return HANDLED;
+                    default:
+                        return NOT_HANDLED;
+                }
+            }
+        }
+
+        class Disconnected extends State {
+            private static final int STATE_NO = STATE_IMS_DISCONNECTED;
+
+            private int mLastMsg = COMMAND_NONE_MSG;
+
+            @Override
+            public void enter() {
+                Log.d(LOG_TAG, "[" + mPhoneId + "]Disconnected state:enter");
+                clearAllMessage();
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                Log.d(LOG_TAG, "[" + mPhoneId + "]Disconnected state:processMessage. msg.what="
+                        + EVENT_DESCRIPTION.get(message.what) + ",component:" + mComponentName);
+
+                switch (message.what) {
+                    case COMMAND_CONNECTED_MSG:
+                        clearAllMessage();
+                        transitionState(mDefault);
+                        if (mLastMsg != COMMAND_NONE_MSG) {
+                            sendMessageDelayed(mLastMsg, mSlotId, mSubId, 0);
+                            mLastMsg = COMMAND_NONE_MSG;
+                        }
+                        return HANDLED;
+                    case COMMAND_ENABLE_MSG:
+                    case COMMAND_DISABLE_MSG:
+                    case COMMAND_RESET_MSG:
+                        mLastMsg = message.what;
+                        mSlotId = message.arg1;
+                        mSubId = message.arg2;
+                        return HANDLED;
+                    default:
+                        return NOT_HANDLED;
+                }
+            }
+        }
+
+        class PostResetting extends State {
+            private static final int STATE_NO = STATE_IMS_POSTRESETTING;
+
+            @Override
+            public void enter() {
+                Log.d(LOG_TAG, "[" + mPhoneId + "]PostResetting state:enter");
+                sendMessageDelayed(COMMAND_POST_RESETTING_DONE, mSlotId, mSubId,
+                        getRemainThrottleTime());
+            }
+
+            @Override
+            public void exit() {
+                mLastMsg = COMMAND_NONE_MSG;
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                Log.d(LOG_TAG, "[" + mPhoneId + "]PostResetting state:processMessage. msg.what="
+                        + EVENT_DESCRIPTION.get(message.what) + ",component:" + mComponentName);
+
+                switch (message.what) {
+                    case COMMAND_POST_RESETTING_DONE:
+                        mSlotId = message.arg1;
+                        mSubId = message.arg2;
+                        if (mLastMsg == COMMAND_DISABLE_MSG) {
+                            sendDisableIms(mSlotId, mSubId);
+                            transitionState(mDisabled);
+                        } else {
+                            // if mLastMsg is COMMAND_NONE_MSG or COMMAND_ENABLE_MSG
+                            sendEnableIms(mSlotId, mSubId);
+                            transitionState(mEnabled);
+                        }
+                        return HANDLED;
+                    case COMMAND_ENABLE_MSG:
+                    case COMMAND_DISABLE_MSG:
+                        mLastMsg = message.what;
+                        mSlotId = message.arg1;
+                        mSubId = message.arg2;
+                        return HANDLED;
+                    case COMMAND_RESET_MSG:
+                        // when resetIms() called again, skip to call
+                        // IImsServiceController.resetIms(slotId, subId), but after throttle time
+                        // IImsServiceController.enableIms(slotId, subId) should be called.
+                        mLastMsg = COMMAND_ENABLE_MSG;
+                        mSlotId = message.arg1;
+                        mSubId = message.arg2;
+                        return HANDLED;
+                    case COMMAND_DISCONNECTED_MSG:
+                        transitionState(mDisconnected);
+                        return HANDLED;
+                    case COMMAND_INVALID_SUBID_MSG:
+                        handleInvalidSubIdMessage();
+                        return HANDLED;
+                    default:
+                        return NOT_HANDLED;
+                }
+            }
+        }
+    }
+
+    public ImsEnablementTracker(Looper looper, ComponentName componentName) {
+        mIImsServiceController = null;
+        mStateMachines = new SparseArray<>();
+        mLooper = looper;
+        mState = ImsEnablementTracker.STATE_IMS_DISCONNECTED;
+        mComponentName = componentName;
+    }
+
+    @VisibleForTesting
+    public ImsEnablementTracker(Looper looper, IImsServiceController controller, int state,
+            int numSlots) {
+        mIImsServiceController = controller;
+        mStateMachines = new SparseArray<>();
+        mLooper = looper;
+        mState = state;
+        mComponentName = null;
+
+        setNumOfSlots(numSlots);
+    }
+
+    /**
+     * Set the number of SIM slots.
+     * @param numOfSlots the number of SIM slots.
+     */
+    public void setNumOfSlots(int numOfSlots) {
+        int oldNumSlots = mStateMachines.size();
+        Log.d(LOG_TAG, "set the slots: old[" + oldNumSlots + "], new[" + numOfSlots + "],"
+                + "component:" + mComponentName);
+        if (numOfSlots == oldNumSlots) {
+            return;
+        }
+        ImsEnablementTrackerStateMachine enablementStateMachine = null;
+        if (oldNumSlots < numOfSlots) {
+            for (int i = oldNumSlots; i < numOfSlots; i++) {
+                enablementStateMachine = new ImsEnablementTrackerStateMachine(
+                        "ImsEnablementTracker", mLooper, mState, i);
+                enablementStateMachine.start();
+                mStateMachines.put(i, enablementStateMachine);
+            }
+        } else if (oldNumSlots > numOfSlots) {
+            for (int i = (oldNumSlots - 1); i > (numOfSlots - 1); i--) {
+                enablementStateMachine = mStateMachines.get(i);
+                mStateMachines.remove(i);
+                enablementStateMachine.quitNow();
+            }
+        }
+    }
+
+    @VisibleForTesting
+    public Handler getHandler(int slotId) {
+        return mStateMachines.get(slotId).getHandler();
+    }
+
+    /**
+     * Check that the current state and the input state are the same.
+     * @param state the input state.
+     * @return true if the current state and input state are the same or false.
+     */
+    @VisibleForTesting
+    public boolean isState(int slotId, int state) {
+        return mStateMachines.get(slotId).isState(state);
+    }
+
+    /**
+     * Notify the state machine that the subId has changed to invalid.
+     * @param slotId subscription id
+     */
+    public void subIdChangedToInvalid(int slotId) {
+        Log.d(LOG_TAG, "[" + slotId + "] subId changed to invalid, component:" + mComponentName);
+        ImsEnablementTrackerStateMachine stateMachine = mStateMachines.get(slotId);
+        if (stateMachine != null) {
+            stateMachine.sendMessage(COMMAND_INVALID_SUBID_MSG, slotId);
+        } else {
+            Log.w(LOG_TAG, "There is no state machine associated with this slotId.");
+        }
+    }
+
+    /**
+     * Notify ImsService to enable IMS for the framework. This will trigger IMS registration and
+     * trigger ImsFeature status updates.
+     * @param slotId slot id
+     * @param subId subscription id
+     */
+    public void enableIms(int slotId, int subId) {
+        Log.d(LOG_TAG, "[" + slotId + "][" + subId + "]enableIms, component:" + mComponentName);
+        ImsEnablementTrackerStateMachine stateMachine = mStateMachines.get(slotId);
+        if (stateMachine != null) {
+            stateMachine.sendMessage(COMMAND_ENABLE_MSG, slotId, subId);
+        } else {
+            Log.w(LOG_TAG, "There is no state machine associated with this slotId.");
+        }
+    }
+
+    /**
+     * Notify ImsService to disable IMS for the framework. This will trigger IMS de-registration and
+     * trigger ImsFeature capability status to become false.
+     * @param slotId slot id
+     * @param subId subscription id
+     */
+    public void disableIms(int slotId, int subId) {
+        Log.d(LOG_TAG, "[" + slotId + "][" + subId + "]disableIms, component:" + mComponentName);
+        ImsEnablementTrackerStateMachine stateMachine = mStateMachines.get(slotId);
+        if (stateMachine != null) {
+            stateMachine.sendMessage(COMMAND_DISABLE_MSG, slotId, subId);
+        } else {
+            Log.w(LOG_TAG, "There is no state machine associated with this slotId.");
+        }
+    }
+
+    /**
+     * Notify ImsService to reset IMS for the framework. This will trigger ImsService to perform
+     * de-registration and release all resource. After that, if enaleIms is called, the ImsService
+     * performs registration and appropriate initialization to bring up all ImsFeatures.
+     * @param slotId slot id
+     * @param subId subscription id
+     */
+    public void resetIms(int slotId, int subId) {
+        Log.d(LOG_TAG, "[" + slotId + "][" + subId + "]resetIms, component:" + mComponentName);
+        ImsEnablementTrackerStateMachine stateMachine = mStateMachines.get(slotId);
+        if (stateMachine != null) {
+            stateMachine.sendMessage(COMMAND_RESET_MSG, slotId, subId);
+        } else {
+            Log.w(LOG_TAG, "There is no state machine associated with this slotId.");
+        }
+    }
+
+    /**
+     * Sets the IImsServiceController instance.
+     */
+    protected void setServiceController(IBinder serviceController) {
+        synchronized (mLock) {
+            mIImsServiceController = IImsServiceController.Stub.asInterface(serviceController);
+            Log.d(LOG_TAG, "setServiceController with Binder:" + mIImsServiceController
+                    + ", component:" + mComponentName);
+            ImsEnablementTrackerStateMachine stateMachine = null;
+            for (int i = 0; i < mStateMachines.size(); i++) {
+                stateMachine = mStateMachines.get(i);
+                if (stateMachine == null) {
+                    Log.w(LOG_TAG, "There is no state machine associated with"
+                            + "the slotId[" + i + "]");
+                    continue;
+                }
+                if (isServiceControllerAvailable()) {
+                    stateMachine.serviceBinderConnected();
+                } else {
+                    stateMachine.serviceBinderDisconnected();
+                }
+            }
+        }
+    }
+
+    protected long getLastOperationTimeMillis() {
+        return mLastImsOperationTimeMs;
+    }
+
+    /**
+     * Get remaining throttle time value
+     * @return remaining throttle time value
+     */
+    @VisibleForTesting
+    public long getRemainThrottleTime() {
+        long remainTime = REQUEST_THROTTLE_TIME_MS - (System.currentTimeMillis()
+                - getLastOperationTimeMillis());
+
+        if (remainTime < 0) {
+            remainTime = 0L;
+        }
+        Log.d(LOG_TAG, "getRemainThrottleTime:" + remainTime);
+
+        return remainTime;
+    }
+
+    /**
+     * Check to see if the service controller is available.
+     * @return true if available, false otherwise
+     */
+    private boolean isServiceControllerAvailable() {
+        if (mIImsServiceController != null) {
+            return true;
+        }
+        Log.d(LOG_TAG, "isServiceControllerAvailable : binder is not alive");
+        return false;
+    }
+
+    private void sendEnableIms(int slotId, int subId) {
+        try {
+            synchronized (mLock) {
+                if (isServiceControllerAvailable()) {
+                    Log.d(LOG_TAG, "[" + slotId + "][" + subId + "]sendEnableIms,"
+                            + "componentName[" + mComponentName + "]");
+                    mIImsServiceController.enableIms(slotId, subId);
+                    mLastImsOperationTimeMs = System.currentTimeMillis();
+                }
+            }
+        } catch (RemoteException e) {
+            Log.w(LOG_TAG, "Couldn't enable IMS: " + e.getMessage());
+        }
+    }
+
+    private void sendDisableIms(int slotId, int subId) {
+        try {
+            synchronized (mLock) {
+                if (isServiceControllerAvailable()) {
+                    Log.d(LOG_TAG, "[" + slotId + "][" + subId + "]sendDisableIms"
+                            + " componentName[" + mComponentName + "]");
+                    mIImsServiceController.disableIms(slotId, subId);
+                    mLastImsOperationTimeMs = System.currentTimeMillis();
+                }
+            }
+        } catch (RemoteException e) {
+            Log.w(LOG_TAG, "Couldn't disable IMS: " + e.getMessage());
+        }
+    }
+
+    private void sendResetIms(int slotId, int subId) {
+        try {
+            synchronized (mLock) {
+                if (isServiceControllerAvailable()) {
+                    Log.d(LOG_TAG, "[" + slotId + "][" + subId + "]sendResetIms");
+                    mIImsServiceController.resetIms(slotId, subId);
+                    mLastImsOperationTimeMs = System.currentTimeMillis();
+                }
+            }
+        } catch (RemoteException e) {
+            Log.w(LOG_TAG, "Couldn't reset IMS: " + e.getMessage());
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/ims/ImsResolver.java b/src/java/com/android/internal/telephony/ims/ImsResolver.java
index 0cb58aa..49b7e62 100644
--- a/src/java/com/android/internal/telephony/ims/ImsResolver.java
+++ b/src/java/com/android/internal/telephony/ims/ImsResolver.java
@@ -30,6 +30,7 @@
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.HandlerExecutor;
+import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PersistableBundle;
@@ -130,6 +131,7 @@
 
     // Delay between dynamic ImsService queries.
     private static final int DELAY_DYNAMIC_QUERY_MS = 5000;
+    private static final HandlerThread sHandlerThread = new HandlerThread(TAG);
 
     private static ImsResolver sInstance;
 
@@ -139,9 +141,9 @@
     public static void make(Context context, String defaultMmTelPackageName,
             String defaultRcsPackageName, int numSlots, ImsFeatureBinderRepository repo) {
         if (sInstance == null) {
-            Looper looper = Looper.getMainLooper();
+            sHandlerThread.start();
             sInstance = new ImsResolver(context, defaultMmTelPackageName, defaultRcsPackageName,
-                    numSlots, repo, looper);
+                    numSlots, repo, sHandlerThread.getLooper());
         }
     }
 
@@ -630,8 +632,13 @@
 
     /**
      * Needs to be called after the constructor to kick off the process of binding to ImsServices.
+     * Should be run on the handler thread of ImsResolver
      */
     public void initialize() {
+        mHandler.post(()-> initializeInternal());
+    }
+
+    private void initializeInternal() {
         mEventLog.log("Initializing");
         Log.i(TAG, "Initializing cache.");
         PhoneConfigurationManager.registerForMultiSimConfigChange(mHandler,
@@ -738,6 +745,15 @@
     }
 
     /**
+     * Notify ImsService to disable IMS for the framework.
+     * And notify ImsService back to enable IMS for the framework.
+     */
+    public void resetIms(int slotId) {
+        getImsServiceControllers(slotId).forEach(
+                (controller) -> controller.resetIms(slotId, getSubId(slotId)));
+    }
+
+    /**
      * Returns the ImsRegistration structure associated with the slotId and feature specified.
      */
     public @Nullable IImsRegistration getImsRegistration(int slotId, int feature) {
diff --git a/src/java/com/android/internal/telephony/ims/ImsServiceController.java b/src/java/com/android/internal/telephony/ims/ImsServiceController.java
index 92e7d71..6af7a08 100644
--- a/src/java/com/android/internal/telephony/ims/ImsServiceController.java
+++ b/src/java/com/android/internal/telephony/ims/ImsServiceController.java
@@ -85,14 +85,49 @@
 
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
+            if (mHandler.getLooper().isCurrentThread()) {
+                onServiceConnectedInternal(name, service);
+            } else {
+                mHandler.post(() -> onServiceConnectedInternal(name, service));
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            if (mHandler.getLooper().isCurrentThread()) {
+                onServiceDisconnectedInternal(name);
+            } else {
+                mHandler.post(() -> onServiceDisconnectedInternal(name));
+            }
+        }
+
+        @Override
+        public void onBindingDied(ComponentName name) {
+            if (mHandler.getLooper().isCurrentThread()) {
+                onBindingDiedInternal(name);
+            } else {
+                mHandler.post(() -> onBindingDiedInternal(name));
+            }
+        }
+
+        @Override
+        public void onNullBinding(ComponentName name) {
+            if (mHandler.getLooper().isCurrentThread()) {
+                onNullBindingInternal(name);
+            } else {
+                mHandler.post(() -> onNullBindingInternal(name));
+            }
+        }
+
+        private void onServiceConnectedInternal(ComponentName name, IBinder service) {
             synchronized (mLock) {
                 mBackoff.stop();
                 mIsBound = true;
                 mIsBinding = false;
                 try {
-                    mLocalLog.log("onServiceConnected");
-                    Log.d(LOG_TAG, "ImsService(" + name + "): onServiceConnected with binder: "
-                            + service);
+                    mLocalLog.log("onServiceConnectedInternal");
+                    Log.d(LOG_TAG, "ImsService(" + name
+                            + "): onServiceConnectedInternal with binder: " + service);
                     setServiceController(service);
                     notifyImsServiceReady();
                     retrieveStaticImsServiceCapabilities();
@@ -118,20 +153,18 @@
             }
         }
 
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
+        private void onServiceDisconnectedInternal(ComponentName name) {
             synchronized (mLock) {
                 mIsBinding = false;
                 cleanupConnection();
             }
-            mLocalLog.log("onServiceDisconnected");
-            Log.w(LOG_TAG, "ImsService(" + name + "): onServiceDisconnected. Waiting...");
+            mLocalLog.log("onServiceDisconnectedInternal");
+            Log.w(LOG_TAG, "ImsService(" + name + "): onServiceDisconnectedInternal. Waiting...");
             // Service disconnected, but we are still technically bound. Waiting for reconnect.
             checkAndReportAnomaly(name);
         }
 
-        @Override
-        public void onBindingDied(ComponentName name) {
+        private void onBindingDiedInternal(ComponentName name) {
             mIsServiceConnectionDead = true;
             synchronized (mLock) {
                 mIsBinding = false;
@@ -141,15 +174,15 @@
                 unbindService();
                 startDelayedRebindToService();
             }
-            Log.w(LOG_TAG, "ImsService(" + name + "): onBindingDied. Starting rebind...");
-            mLocalLog.log("onBindingDied, retrying in " + mBackoff.getCurrentDelay() + " mS");
+            Log.w(LOG_TAG, "ImsService(" + name + "): onBindingDiedInternal. Starting rebind...");
+            mLocalLog.log("onBindingDiedInternal, retrying in "
+                    + mBackoff.getCurrentDelay() + " mS");
         }
 
-        @Override
-        public void onNullBinding(ComponentName name) {
-            Log.w(LOG_TAG, "ImsService(" + name + "): onNullBinding. Is service dead = "
+        private void onNullBindingInternal(ComponentName name) {
+            Log.w(LOG_TAG, "ImsService(" + name + "): onNullBindingInternal. Is service dead = "
                     + mIsServiceConnectionDead);
-            mLocalLog.log("onNullBinding, is service dead = " + mIsServiceConnectionDead);
+            mLocalLog.log("onNullBindingInternal, is service dead = " + mIsServiceConnectionDead);
             // onNullBinding will happen after onBindingDied. In this case, we should not
             // permanently unbind and instead let the automatic rebind occur.
             if (mIsServiceConnectionDead) return;
@@ -230,6 +263,7 @@
     private static final boolean ENFORCE_SINGLE_SERVICE_FOR_SIP_TRANSPORT = false;
     private final ComponentName mComponentName;
     private final HandlerThread mHandlerThread = new HandlerThread("ImsServiceControllerHandler");
+    private final Handler mHandler;
     private final LegacyPermissionManager mPermissionManager;
     private ImsFeatureBinderRepository mRepo;
     private ImsServiceControllerCallbacks mCallbacks;
@@ -241,6 +275,7 @@
     private Set<ImsFeatureConfiguration.FeatureSlotPair> mImsFeatures;
     private SparseIntArray mSlotIdToSubIdMap;
     private IImsServiceController mIImsServiceController;
+    private final ImsEnablementTracker mImsEnablementTracker;
     // The Capabilities bitmask of the connected ImsService (see ImsService#ImsServiceCapability).
     private long mServiceCapabilities;
     private ImsServiceConnection mImsServiceConnection;
@@ -323,16 +358,17 @@
         mComponentName = componentName;
         mCallbacks = callbacks;
         mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
         mBackoff = new ExponentialBackoff(
                 mRebindRetry.getStartDelay(),
                 mRebindRetry.getMaximumDelay(),
                 2, /* multiplier */
-                mHandlerThread.getLooper(),
+                mHandler,
                 mRestartImsServiceRunnable);
         mPermissionManager = (LegacyPermissionManager) mContext.getSystemService(
                 Context.LEGACY_PERMISSION_SERVICE);
         mRepo = repo;
-
+        mImsEnablementTracker = new ImsEnablementTracker(mHandlerThread.getLooper(), componentName);
         mPackageManager = mContext.getPackageManager();
         if (mPackageManager != null) {
             mChangedPackages = mPackageManager.getChangedPackages(mLastSequenceNumber);
@@ -351,6 +387,7 @@
         mContext = context;
         mComponentName = componentName;
         mCallbacks = callbacks;
+        mHandler = handler;
         mBackoff = new ExponentialBackoff(
                 rebindRetry.getStartDelay(),
                 rebindRetry.getMaximumDelay(),
@@ -359,6 +396,7 @@
                 mRestartImsServiceRunnable);
         mPermissionManager = null;
         mRepo = repo;
+        mImsEnablementTracker = new ImsEnablementTracker(handler.getLooper(), componentName);
     }
 
     /**
@@ -378,6 +416,8 @@
                 sanitizeFeatureConfig(imsFeatureSet);
                 mImsFeatures = imsFeatureSet;
                 mSlotIdToSubIdMap = slotIdToSubIdMap;
+                // Set the number of slots that support the feature
+                mImsEnablementTracker.setNumOfSlots(mSlotIdToSubIdMap.size());
                 grantPermissionsToService();
                 Intent imsServiceIntent = new Intent(getServiceInterface()).setComponent(
                         mComponentName);
@@ -464,6 +504,14 @@
                             + newSubId);
                     Log.i(LOG_TAG, "subId changed for slot: " + slotID + ", " + oldSubId + " -> "
                             + newSubId);
+                    if (newSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                        /* An INVALID subId can also be set in bind(), however
+                        the ImsEnablementTracker will move into the DEFAULT state, so we only
+                        need to track changes in subId that result in requiring we move
+                        the state machine back to DEFAULT.
+                         */
+                        mImsEnablementTracker.subIdChangedToInvalid(slotID);
+                    }
                 }
             }
             mSlotIdToSubIdMap = slotIdToSubIdMap;
@@ -553,15 +601,7 @@
      * trigger ImsFeature status updates.
      */
     public void enableIms(int slotId, int subId) {
-        try {
-            synchronized (mLock) {
-                if (isServiceControllerAvailable()) {
-                    mIImsServiceController.enableIms(slotId, subId);
-                }
-            }
-        } catch (RemoteException e) {
-            Log.w(LOG_TAG, "Couldn't enable IMS: " + e.getMessage());
-        }
+        mImsEnablementTracker.enableIms(slotId, subId);
     }
 
     /**
@@ -569,15 +609,15 @@
      * trigger ImsFeature capability status to become false.
      */
     public void disableIms(int slotId, int subId) {
-        try {
-            synchronized (mLock) {
-                if (isServiceControllerAvailable()) {
-                    mIImsServiceController.disableIms(slotId, subId);
-                }
-            }
-        } catch (RemoteException e) {
-            Log.w(LOG_TAG, "Couldn't disable IMS: " + e.getMessage());
-        }
+        mImsEnablementTracker.disableIms(slotId, subId);
+    }
+
+    /**
+     * Notify ImsService to disable IMS for the framework.
+     * And notify ImsService back to enable IMS for the framework
+     */
+    public void resetIms(int slotId, int subId) {
+        mImsEnablementTracker.resetIms(slotId, subId);
     }
 
     /**
@@ -651,6 +691,7 @@
      */
     protected void setServiceController(IBinder serviceController) {
         mIImsServiceController = IImsServiceController.Stub.asInterface(serviceController);
+        mImsEnablementTracker.setServiceController(serviceController);
     }
 
     /**
@@ -910,7 +951,7 @@
             return;
         }
         ChangedPackages curChangedPackages =
-                            mPackageManager.getChangedPackages(mLastSequenceNumber);
+                mPackageManager.getChangedPackages(mLastSequenceNumber);
         if (curChangedPackages != null) {
             mLastSequenceNumber = curChangedPackages.getSequenceNumber();
             List<String> packagesNames = curChangedPackages.getPackageNames();
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsCallInfo.java b/src/java/com/android/internal/telephony/imsphone/ImsCallInfo.java
new file mode 100644
index 0000000..79ab9c5
--- /dev/null
+++ b/src/java/com/android/internal/telephony/imsphone/ImsCallInfo.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2022 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.imsphone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.ServiceState;
+
+import com.android.internal.telephony.Call;
+
+/**
+ * Contains call state to be notified to modem.
+ */
+public class ImsCallInfo {
+
+    private final int mIndex;
+    private @Nullable ImsPhoneConnection mConnection = null;
+    private Call.State mState = Call.State.IDLE;
+    private boolean mIsHeldByRemote = false;
+
+    public ImsCallInfo(int index) {
+        mIndex = index;
+    }
+
+    /** Clears the call state. */
+    public void reset() {
+        mConnection = null;
+        mState = Call.State.IDLE;
+        mIsHeldByRemote = false;
+    }
+
+    /**
+     * Updates the state of the IMS call.
+     *
+     * @param c The instance of {@link ImsPhoneConnection}.
+     */
+    public void update(@NonNull ImsPhoneConnection c) {
+        mConnection = c;
+        mState = c.getState();
+    }
+
+    /**
+     * Updates the state of the IMS call.
+     *
+     * @param c The instance of {@link ImsPhoneConnection}.
+     * @param holdReceived {@code true} if the remote party held the call.
+     * @param resumeReceived {@code true} if the remote party resumed the call.
+     */
+    public boolean update(@NonNull ImsPhoneConnection c,
+            boolean holdReceived, boolean resumeReceived) {
+        Call.State state = c.getState();
+        boolean changed = mState != state;
+        mState = state;
+
+        if (holdReceived && !mIsHeldByRemote) {
+            changed = true;
+            mIsHeldByRemote = true;
+        } else if (resumeReceived && mIsHeldByRemote) {
+            changed = true;
+            mIsHeldByRemote = false;
+        }
+
+        return changed;
+    }
+
+    /** Called when clearing orphaned connection. */
+    public void onDisconnect() {
+        mState = Call.State.DISCONNECTED;
+    }
+
+    /** @return the call index. */
+    public int getIndex() {
+        return mIndex;
+    }
+
+    /** @return the call state. */
+    public Call.State getCallState() {
+        return mState;
+    }
+
+    /** @return whether the remote party is holding the call. */
+    public boolean isHeldByRemote() {
+        return mIsHeldByRemote;
+    }
+
+    /** @return {@code true} if the call is an incoming call. */
+    public boolean isIncoming() {
+        return mConnection.isIncoming();
+    }
+
+    /** @return {@code true} if the call is an emergency call. */
+    public boolean isEmergencyCall() {
+        return mConnection.isEmergencyCall();
+    }
+
+    /** @return the radio technology used for current connection. */
+    public @AccessNetworkConstants.RadioAccessNetworkType int getCallRadioTech() {
+        return ServiceState.rilRadioTechnologyToAccessNetworkType(mConnection.getCallRadioTech());
+    }
+
+    @Override
+    public String toString() {
+        return "[ id=" + mIndex + ", state=" + mState
+                + ", isMT=" + isIncoming() + ", heldByRemote=" + mIsHeldByRemote + " ]";
+    }
+}
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsCallInfoTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsCallInfoTracker.java
new file mode 100644
index 0000000..5783e48
--- /dev/null
+++ b/src/java/com/android/internal/telephony/imsphone/ImsCallInfoTracker.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2022 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.imsphone;
+
+import static com.android.internal.telephony.Call.State.DISCONNECTED;
+import static com.android.internal.telephony.Call.State.IDLE;
+
+import android.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.Phone;
+import com.android.telephony.Rlog;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Contains the state of all IMS calls.
+ */
+public class ImsCallInfoTracker {
+    private static final String LOG_TAG = "ImsCallInfoTracker";
+    private static final boolean DBG = false;
+
+    private final Phone mPhone;
+    private final List<ImsCallInfo> mQueue = new ArrayList<>();
+    private int mNextIndex = 1;
+
+    private final Map<Connection, ImsCallInfo> mImsCallInfo = new HashMap<>();
+
+    public ImsCallInfoTracker(Phone phone) {
+        mPhone = phone;
+    }
+
+    /**
+     * Adds a new instance of the IMS call.
+     *
+     * @param c The instance of {@link ImsPhoneConnection}.
+     */
+    public void addImsCallStatus(@NonNull ImsPhoneConnection c) {
+        if (DBG) Rlog.d(LOG_TAG, "addImsCallStatus");
+
+        synchronized (mImsCallInfo) {
+            if (mQueue.isEmpty()) {
+                mQueue.add(new ImsCallInfo(mNextIndex++));
+            }
+
+            Iterator<ImsCallInfo> it = mQueue.iterator();
+            ImsCallInfo imsCallInfo = it.next();
+            mQueue.remove(imsCallInfo);
+
+            imsCallInfo.update(c);
+            mImsCallInfo.put(c, imsCallInfo);
+
+            notifyImsCallStatus();
+
+            if (DBG) dump();
+        }
+    }
+
+    /**
+     * Updates the list of IMS calls.
+     *
+     * @param c The instance of {@link ImsPhoneConnection}.
+     */
+    public void updateImsCallStatus(@NonNull ImsPhoneConnection c) {
+        updateImsCallStatus(c, false, false);
+    }
+
+    /**
+     * Updates the list of IMS calls.
+     *
+     * @param c The instance of {@link ImsPhoneConnection}.
+     * @param holdReceived {@code true} if the remote party held the call.
+     * @param resumeReceived {@code true} if the remote party resumed the call.
+     */
+    public void updateImsCallStatus(@NonNull ImsPhoneConnection c,
+            boolean holdReceived, boolean resumeReceived) {
+        if (DBG) {
+            Rlog.d(LOG_TAG, "updateImsCallStatus holdReceived=" + holdReceived
+                    + ", resumeReceived=" + resumeReceived);
+        }
+
+        synchronized (mImsCallInfo) {
+            ImsCallInfo info = mImsCallInfo.get(c);
+
+            if (info == null) {
+                // This happens when the user tries to hangup the call after handover has completed.
+                return;
+            }
+
+            boolean changed = info.update(c, holdReceived, resumeReceived);
+
+            if (changed) notifyImsCallStatus();
+
+            Call.State state = c.getState();
+
+            if (DBG) Rlog.d(LOG_TAG, "updateImsCallStatus state=" + state);
+            // Call is disconnected. There are 2 cases in disconnected state:
+            // if silent redial, state == IDLE, otherwise, state == DISCONNECTED.
+            if (state == DISCONNECTED || state == IDLE) {
+                // clear the disconnected call
+                mImsCallInfo.remove(c);
+                info.reset();
+                if (info.getIndex() < (mNextIndex - 1)) {
+                    mQueue.add(info);
+                    sort(mQueue);
+                } else {
+                    mNextIndex--;
+                }
+            }
+
+            if (DBG) dump();
+        }
+    }
+
+    /** Clears all orphaned IMS call information. */
+    public void clearAllOrphanedConnections() {
+        if (DBG) Rlog.d(LOG_TAG, "clearAllOrphanedConnections");
+
+        Collection<ImsCallInfo> infos = mImsCallInfo.values();
+        infos.stream().forEach(info -> { info.onDisconnect(); });
+        notifyImsCallStatus();
+        clearAllCallInfo();
+
+        if (DBG) dump();
+    }
+
+    /** Notifies that SRVCC has completed. */
+    public void notifySrvccCompleted() {
+        if (DBG) Rlog.d(LOG_TAG, "notifySrvccCompleted");
+
+        clearAllCallInfo();
+        notifyImsCallStatus();
+
+        if (DBG) dump();
+    }
+
+    private void clearAllCallInfo() {
+        try {
+            Collection<ImsCallInfo> infos = mImsCallInfo.values();
+            infos.stream().forEach(info -> { info.reset(); });
+            mImsCallInfo.clear();
+            mQueue.clear();
+            mNextIndex = 1;
+        } catch (UnsupportedOperationException e) {
+            Rlog.e(LOG_TAG, "e=" + e);
+        }
+    }
+
+    private void notifyImsCallStatus() {
+        Collection<ImsCallInfo> infos = mImsCallInfo.values();
+        ArrayList<ImsCallInfo> imsCallInfo = new ArrayList<ImsCallInfo>(infos);
+        sort(imsCallInfo);
+        mPhone.updateImsCallStatus(imsCallInfo, null);
+    }
+
+    /**
+     * Sorts the list of IMS calls by the call index.
+     *
+     * @param infos The list of IMS calls.
+     */
+    @VisibleForTesting
+    public static void sort(List<ImsCallInfo> infos) {
+        Collections.sort(infos, new Comparator<ImsCallInfo>() {
+            @Override
+            public int compare(ImsCallInfo l, ImsCallInfo r) {
+                if (l.getIndex() > r.getIndex()) {
+                    return 1;
+                } else if (l.getIndex() < r.getIndex()) {
+                    return -1;
+                }
+                return 0;
+            }
+        });
+    }
+
+    private void dump() {
+        Collection<ImsCallInfo> infos = mImsCallInfo.values();
+        ArrayList<ImsCallInfo> imsCallInfo = new ArrayList<ImsCallInfo>(infos);
+        sort(imsCallInfo);
+        Rlog.d(LOG_TAG, "imsCallInfos=" + imsCallInfo);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsNrSaModeHandler.java b/src/java/com/android/internal/telephony/imsphone/ImsNrSaModeHandler.java
new file mode 100644
index 0000000..3dedde8
--- /dev/null
+++ b/src/java/com/android/internal/telephony/imsphone/ImsNrSaModeHandler.java
@@ -0,0 +1,355 @@
+/*
+ * 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.imsphone;
+
+import static android.telephony.CarrierConfigManager.CARRIER_NR_AVAILABILITY_SA;
+import static android.telephony.CarrierConfigManager.Ims.KEY_NR_SA_DISABLE_POLICY_INT;
+import static android.telephony.CarrierConfigManager.Ims.NR_SA_DISABLE_POLICY_NONE;
+import static android.telephony.CarrierConfigManager.Ims.NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED;
+import static android.telephony.CarrierConfigManager.Ims.NR_SA_DISABLE_POLICY_WFC_ESTABLISHED;
+import static android.telephony.CarrierConfigManager.Ims.NR_SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED;
+import static android.telephony.CarrierConfigManager.Ims.NrSaDisablePolicy;
+import static android.telephony.CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY;
+import static android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech;
+import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.Call;
+
+import java.util.Arrays;
+import java.util.Set;
+
+/**
+ * Enables or Disables NR-SA mode temporarily under certain conditions where WFC is established or
+ * IMS is registered over WiFi in order to improve the delay or voice mute issue when the handover
+ * from ePDG to NR is not supported in UE or network.
+ */
+public class ImsNrSaModeHandler extends Handler{
+
+    public static final String TAG = "ImsNrSaModeHandler";
+    public static final String MMTEL_FEATURE_TAG =
+            "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.mmtel\"";
+
+    private static final int MSG_PRECISE_CALL_STATE_CHANGED = 101;
+    private static final int MSG_REQUEST_IS_VONR_ENABLED = 102;
+    private static final int MSG_RESULT_IS_VONR_ENABLED = 103;
+
+    private final @NonNull ImsPhone mPhone;
+    private @Nullable CarrierConfigManager mCarrierConfigManager;
+
+    private @NrSaDisablePolicy int mNrSaDisablePolicy;
+    private boolean mIsNrSaDisabledForWfc;
+    private boolean mIsVowifiRegistered;
+    private boolean mIsInImsCall;
+    private boolean mIsNrSaSupported;
+
+    private final CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener =
+            (slotIndex, subId, carrierId, specificCarrierId) -> setNrSaDisablePolicy(subId);
+
+    public ImsNrSaModeHandler(@NonNull ImsPhone phone, Looper looper) {
+        super(looper);
+        mPhone = phone;
+        mCarrierConfigManager = (CarrierConfigManager) mPhone.getContext()
+                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+
+        registerForCarrierConfigChanges();
+    }
+
+    /**
+     * Performs any cleanup required before the ImsNrSaModeHandler is destroyed.
+     */
+    public void tearDown() {
+        unregisterForCarrierConfigChanges();
+        unregisterForPreciseCallStateChanges();
+
+        if (isNrSaDisabledForWfc()) {
+            setNrSaMode(true);
+        }
+    }
+
+    /**
+     * Based on changed VoWiFi reg state and call state, handles NR SA mode if needed.
+     * It is including handover case.
+     *
+     * @param imsRadioTech The current registered RAT.
+     */
+    public void onImsRegistered(
+            @ImsRegistrationTech int imsRadioTech, @NonNull Set<String> featureTags) {
+        if (mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_NONE) {
+            return;
+        }
+
+        Log.d(TAG, "onImsRegistered: ImsRegistrationTech = " + imsRadioTech);
+
+        boolean isVowifiRegChanged = false;
+
+        if (isVowifiRegistered() && imsRadioTech != REGISTRATION_TECH_IWLAN) {
+            setVowifiRegStatus(false);
+            isVowifiRegChanged = true;
+        } else if (!isVowifiRegistered() && imsRadioTech == REGISTRATION_TECH_IWLAN
+                && featureTags.contains(MMTEL_FEATURE_TAG)) {
+            setVowifiRegStatus(true);
+            isVowifiRegChanged = true;
+        }
+
+        if (isVowifiRegChanged) {
+            if (mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED) {
+                setNrSaMode(!isVowifiRegistered());
+            } else if ((mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_WFC_ESTABLISHED
+                    || mNrSaDisablePolicy
+                    == NR_SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED)
+                    && isImsCallOngoing()) {
+                if (mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED) {
+                    requestIsVonrEnabled(!isVowifiRegistered());
+                    return;
+                }
+
+                setNrSaMode(!isVowifiRegistered());
+            }
+        }
+    }
+
+    /**
+     * Based on changed VoWiFi reg state and call state, handles NR SA mode if needed.
+     *
+     * @param imsRadioTech The current un-registered RAT.
+     */
+    public void onImsUnregistered(
+            @ImsRegistrationTech int imsRadioTech) {
+        if (mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_NONE
+                || imsRadioTech != REGISTRATION_TECH_IWLAN || !isVowifiRegistered()) {
+            return;
+        }
+
+        Log.d(TAG, "onImsUnregistered : ImsRegistrationTech = " + imsRadioTech);
+
+        setVowifiRegStatus(false);
+
+        if (mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED) {
+            setNrSaMode(true);
+        } else if ((mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_WFC_ESTABLISHED
+                || mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED)
+                && isImsCallOngoing()) {
+            if (mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED) {
+                requestIsVonrEnabled(true);
+                return;
+            }
+
+            setNrSaMode(true);
+        }
+    }
+
+    /**
+     * Based on changed precise call state and VoWiFi reg state, handles NR SA mode if needed.
+     */
+    public void onPreciseCallStateChanged() {
+        Log.d(TAG, "onPreciseCallStateChanged :  foreground state = "
+                + mPhone.getForegroundCall().getState() + ", background state = "
+                + mPhone.getBackgroundCall().getState());
+
+        boolean isImsCallStatusChanged = false;
+
+        if (isImsCallJustEstablished()) {
+            setImsCallStatus(true);
+            isImsCallStatusChanged = true;
+        } else if (isImsCallJustTerminated()) {
+            setImsCallStatus(false);
+            isImsCallStatusChanged = true;
+        }
+
+        if (isVowifiRegistered() && isImsCallStatusChanged) {
+            if (mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED) {
+                requestIsVonrEnabled(!isImsCallOngoing());
+                return;
+            }
+
+            setNrSaMode(!isImsCallOngoing());
+        }
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        AsyncResult ar;
+
+        switch (msg.what) {
+            case MSG_PRECISE_CALL_STATE_CHANGED :
+                onPreciseCallStateChanged();
+                break;
+            case MSG_REQUEST_IS_VONR_ENABLED :
+                Log.d(TAG, "request isVoNrEnabled");
+                mPhone.getDefaultPhone().mCi.isVoNrEnabled(
+                        obtainMessage(MSG_RESULT_IS_VONR_ENABLED, msg.obj), null);
+                break;
+            case MSG_RESULT_IS_VONR_ENABLED :
+                ar = (AsyncResult) msg.obj;
+
+                if (ar.result != null) {
+                    boolean vonrEnabled = ((Boolean) ar.result).booleanValue();
+
+                    Log.d(TAG, "result isVoNrEnabled = " + vonrEnabled);
+                    if (!vonrEnabled) {
+                        setNrSaMode(((Boolean) ar.userObj).booleanValue());
+                    }
+                }
+
+                ar = null;
+                break;
+            default :
+                break;
+        }
+    }
+
+    /**
+     * Registers for precise call state changes.
+     */
+    private void registerForPreciseCallStateChanges() {
+        mPhone.registerForPreciseCallStateChanged(this, MSG_PRECISE_CALL_STATE_CHANGED, null);
+    }
+
+    /**
+     * Unregisters for precise call state changes.
+     */
+    private void unregisterForPreciseCallStateChanges() {
+        mPhone.unregisterForPreciseCallStateChanged(this);
+    }
+
+    /**
+     * Registers for carrier config changes.
+     */
+    private void registerForCarrierConfigChanges() {
+        if (mCarrierConfigManager != null) {
+            mCarrierConfigManager.registerCarrierConfigChangeListener(
+                    this::post, mCarrierConfigChangeListener);
+        }
+    }
+
+    /**
+     * Unregisters for carrier config changes.
+     */
+    private void unregisterForCarrierConfigChanges() {
+        if (mCarrierConfigManager != null) {
+            mCarrierConfigManager.unregisterCarrierConfigChangeListener(
+                    mCarrierConfigChangeListener);
+        }
+    }
+
+    private void setNrSaDisablePolicy(int subId) {
+        if (mPhone.getSubId() == subId && mCarrierConfigManager != null) {
+            PersistableBundle bundle = mCarrierConfigManager.getConfigForSubId(mPhone.getSubId(),
+                    KEY_NR_SA_DISABLE_POLICY_INT, KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY);
+            mNrSaDisablePolicy = bundle.getInt(KEY_NR_SA_DISABLE_POLICY_INT);
+            mIsNrSaSupported = Arrays.stream(
+                    bundle.getIntArray(KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY)).anyMatch(
+                        value -> value == CARRIER_NR_AVAILABILITY_SA);
+
+            Log.d(TAG, "setNrSaDisablePolicy : NrSaDisablePolicy = "
+                    + mNrSaDisablePolicy + ", IsNrSaSupported = "  + mIsNrSaSupported);
+
+            if (mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED
+                    || mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_WFC_ESTABLISHED) {
+                registerForPreciseCallStateChanges();
+            } else {
+                unregisterForPreciseCallStateChanges();
+            }
+        }
+    }
+
+    private void requestIsVonrEnabled(boolean onOrOff) {
+        Message msg = obtainMessage(MSG_REQUEST_IS_VONR_ENABLED, onOrOff);
+        msg.sendToTarget();
+    }
+
+    private void setNrSaMode(boolean onOrOff) {
+        if (mIsNrSaSupported) {
+            mPhone.getDefaultPhone().mCi.setN1ModeEnabled(onOrOff, null);
+            Log.i(TAG, "setNrSaMode : " + onOrOff);
+
+            setNrSaDisabledForWfc(!onOrOff);
+        }
+    }
+
+    /**
+     * Sets VoWiFi reg status.
+     */
+    @VisibleForTesting
+    public void setVowifiRegStatus(boolean registered) {
+        Log.d(TAG, "setVowifiRegStatus : " + registered);
+        mIsVowifiRegistered = registered;
+    }
+
+    /**
+     * Sets IMS call status
+     */
+    @VisibleForTesting
+    public void setImsCallStatus(boolean inImsCall) {
+        Log.d(TAG, "setImsCallStatus : " + inImsCall);
+        mIsInImsCall = inImsCall;
+    }
+
+    @VisibleForTesting
+    public boolean isVowifiRegistered() {
+        return mIsVowifiRegistered;
+    }
+
+    @VisibleForTesting
+    public boolean isImsCallOngoing() {
+        return mIsInImsCall;
+    }
+
+    @VisibleForTesting
+    public boolean isNrSaDisabledForWfc() {
+        return mIsNrSaDisabledForWfc;
+    }
+
+    @VisibleForTesting
+    public void setNrSaDisabledForWfc(boolean disabled) {
+        mIsNrSaDisabledForWfc = disabled;
+    }
+
+    private boolean isImsCallJustEstablished() {
+        if (!isImsCallOngoing()) {
+            if ((mPhone.getForegroundCall().getState() == Call.State.ACTIVE)
+                    || (mPhone.getBackgroundCall().getState() == Call.State.ACTIVE)) {
+                Log.d(TAG, "isImsCallJustEstablished");
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private boolean isImsCallJustTerminated() {
+        if (isImsCallOngoing() && (!mPhone.getForegroundCall().getState().isAlive()
+                && !mPhone.getBackgroundCall().getState().isAlive())) {
+            Log.d(TAG, "isImsCallJustTerminated");
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
index 5c14b0e..4b150cc 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -16,9 +16,14 @@
 
 package com.android.internal.telephony.imsphone;
 
-import static android.provider.Telephony.SimInfo.COLUMN_PHONE_NUMBER_SOURCE_IMS;
 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_PLMN_BLOCK;
+import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT;
+import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NONE;
 
 import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BAIC;
 import static com.android.internal.telephony.CommandsInterface.CB_FACILITY_BAICr;
@@ -42,6 +47,7 @@
 import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_NONE;
 import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_VOICE;
 
+import android.annotation.NonNull;
 import android.app.Activity;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -71,7 +77,6 @@
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.ServiceState;
-import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.UssdResponse;
@@ -79,9 +84,12 @@
 import android.telephony.ims.ImsCallForwardInfo;
 import android.telephony.ims.ImsCallProfile;
 import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsRegistrationAttributes;
 import android.telephony.ims.ImsSsData;
 import android.telephony.ims.ImsSsInfo;
 import android.telephony.ims.RegistrationManager;
+import android.telephony.ims.feature.MmTelFeature;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.telephony.ims.stub.ImsUtImplBase;
 import android.text.TextUtils;
 import android.util.LocalLog;
@@ -107,10 +115,11 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneNotifier;
 import com.android.internal.telephony.ServiceStateTracker;
-import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.TelephonyComponentFactory;
 import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.domainselection.DomainSelectionResolver;
 import com.android.internal.telephony.emergency.EmergencyNumberTracker;
+import com.android.internal.telephony.emergency.EmergencyStateTracker;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
 import com.android.internal.telephony.metrics.ImsStats;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
@@ -184,6 +193,7 @@
                     return new ImsDialArgs.Builder()
                             .setUusInfo(dialArgs.uusInfo)
                             .setIsEmergency(dialArgs.isEmergency)
+                            .setEccCategory(dialArgs.eccCategory)
                             .setVideoState(dialArgs.videoState)
                             .setIntentExtras(dialArgs.intentExtras)
                             .setRttTextStream(((ImsDialArgs)dialArgs).rttTextStream)
@@ -253,6 +263,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     ImsPhoneCallTracker mCT;
     ImsExternalCallTracker mExternalCallTracker;
+    ImsNrSaModeHandler mImsNrSaModeHandler;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private ArrayList <ImsPhoneMmiCode> mPendingMMIs = new ArrayList<ImsPhoneMmiCode>();
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -289,6 +300,16 @@
 
     private ImsStats mImsStats;
 
+    private int mImsRegistrationState;
+    // The access network type where IMS is registered
+    private @ImsRegistrationImplBase.ImsRegistrationTech int mImsRegistrationTech =
+            REGISTRATION_TECH_NONE;
+    private @RegistrationManager.SuggestedAction int mImsRegistrationSuggestedAction;
+    private @ImsRegistrationImplBase.ImsRegistrationTech int mImsDeregistrationTech =
+            REGISTRATION_TECH_NONE;
+    private int mImsRegistrationCapabilities;
+    private boolean mNotifiedRegisteredState;
+
     // A runnable which is used to automatically exit from Ecm after a period of time.
     private Runnable mExitEcmRunnable = new Runnable() {
         @Override
@@ -453,16 +474,16 @@
                 TelephonyComponentFactory.getInstance()
                         .inject(ImsExternalCallTracker.class.getName())
                         .makeImsExternalCallTracker(this);
+        mImsNrSaModeHandler =
+                TelephonyComponentFactory.getInstance()
+                        .inject(ImsNrSaModeHandler.class.getName())
+                        .makeImsNrSaModeHandler(this);
         mCT = TelephonyComponentFactory.getInstance().inject(ImsPhoneCallTracker.class.getName())
                 .makeImsPhoneCallTracker(this);
         mCT.registerPhoneStateListener(mExternalCallTracker);
         mExternalCallTracker.setCallPuller(mCT);
 
-        boolean legacyMode = true;
-        if (mDefaultPhone.getAccessNetworksManager() != null) {
-            legacyMode = mDefaultPhone.getAccessNetworksManager().isInLegacyMode();
-        }
-        mSS.setOutOfService(legacyMode, false);
+        mSS.setOutOfService(false);
 
         mPhoneId = mDefaultPhone.getPhoneId();
 
@@ -504,6 +525,7 @@
         //super.dispose();
         mPendingMMIs.clear();
         mExternalCallTracker.tearDown();
+        mImsNrSaModeHandler.tearDown();
         mCT.unregisterPhoneStateListener(mExternalCallTracker);
         mCT.unregisterForVoiceCallEnded(this);
         mCT.dispose();
@@ -892,6 +914,9 @@
 
     @Override
     public boolean isInImsEcm() {
+        if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
+            return EmergencyStateTracker.getInstance().isInImsEcm();
+        }
         return mIsInImsEcm;
     }
 
@@ -1584,7 +1609,7 @@
     }
 
     @Override
-    public void notifySrvccState(Call.SrvccState state) {
+    public void notifySrvccState(int state) {
         mCT.notifySrvccState(state);
     }
 
@@ -2038,6 +2063,10 @@
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private void handleEnterEmergencyCallbackMode() {
+        if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
+            logd("DomainSelection enabled: ignore ECBM enter event.");
+            return;
+        }
         if (DBG) logd("handleEnterEmergencyCallbackMode,mIsPhoneInEcmState= " + isInEcm());
         // if phone is not in Ecm mode, and it's changed to Ecm mode
         if (!isInEcm()) {
@@ -2059,6 +2088,10 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @Override
     protected void handleExitEmergencyCallbackMode() {
+        if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
+            logd("DomainSelection enabled: ignore ECBM exit event.");
+            return;
+        }
         if (DBG) logd("handleExitEmergencyCallbackMode: mIsPhoneInEcmState = " + isInEcm());
 
         if (isInEcm()) {
@@ -2088,6 +2121,7 @@
      * Ecm timer and notify apps the timer is restarted.
      */
     void handleTimerInEmergencyCallbackMode(int action) {
+        if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) return;
         switch (action) {
             case CANCEL_ECM_TIMER:
                 removeCallbacks(mExitEcmRunnable);
@@ -2371,7 +2405,7 @@
     /**
      * Update roaming state and WFC mode in the following situations:
      *     1) voice is in service.
-     *     2) data is in service and it is not IWLAN (if in legacy mode).
+     *     2) data is in service.
      * @param ss non-null ServiceState
      */
     private void updateRoamingState(ServiceState ss) {
@@ -2392,15 +2426,7 @@
             logi("updateRoamingState: we are not IN_SERVICE, ignoring roaming change.");
             return;
         }
-        // We ignore roaming changes when moving to IWLAN because it always sets the roaming
-        // mode to home and masks the actual cellular roaming status if voice is not registered. If
-        // we just moved to IWLAN because WFC roaming mode is IWLAN preferred and WFC home mode is
-        // cell preferred, we can get into a condition where the modem keeps bouncing between
-        // IWLAN->cell->IWLAN->cell...
-        if (isCsNotInServiceAndPsWwanReportingWlan(ss)) {
-            logi("updateRoamingState: IWLAN masking roaming, ignore roaming change.");
-            return;
-        }
+
         if (mCT.getState() == PhoneConstants.State.IDLE) {
             if (DBG) logd("updateRoamingState now: " + newRoamingState);
             mLastKnownRoamingState = newRoamingState;
@@ -2419,30 +2445,6 @@
         }
     }
 
-    /**
-     * In legacy mode, data registration will report IWLAN when we are using WLAN for data,
-     * effectively masking the true roaming state of the device if voice is not registered.
-     *
-     * @return true if we are reporting not in service for CS domain over WWAN transport and WLAN
-     * for PS domain over WWAN transport.
-     */
-    private boolean isCsNotInServiceAndPsWwanReportingWlan(ServiceState ss) {
-        // We can not get into this condition if we are in AP-Assisted mode.
-        if (mDefaultPhone.getAccessNetworksManager() == null
-                || !mDefaultPhone.getAccessNetworksManager().isInLegacyMode()) {
-            return false;
-        }
-        NetworkRegistrationInfo csInfo = ss.getNetworkRegistrationInfo(
-                NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-        NetworkRegistrationInfo psInfo = ss.getNetworkRegistrationInfo(
-                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-        // We will return roaming state correctly if the CS domain is in service because
-        // ss.getRoaming() returns isVoiceRoaming||isDataRoaming result and isDataRoaming==false
-        // when the modem reports IWLAN RAT.
-        return psInfo != null && csInfo != null && !csInfo.isInService()
-                && psInfo.getAccessNetworkTechnology() == TelephonyManager.NETWORK_TYPE_IWLAN;
-    }
-
     public RegistrationManager.RegistrationCallback getImsMmTelRegistrationCallback() {
         return mImsMmTelRegistrationHelper.getCallback();
     }
@@ -2453,12 +2455,18 @@
     public void resetImsRegistrationState() {
         if (DBG) logd("resetImsRegistrationState");
         mImsMmTelRegistrationHelper.reset();
+        int subId = getSubId();
+        if (SubscriptionManager.isValidSubscriptionId(subId)) {
+            updateImsRegistrationInfo(REGISTRATION_STATE_NOT_REGISTERED,
+                    REGISTRATION_TECH_NONE, SUGGESTED_ACTION_NONE);
+        }
     }
 
     private ImsRegistrationCallbackHelper.ImsRegistrationUpdate mMmTelRegistrationUpdate = new
             ImsRegistrationCallbackHelper.ImsRegistrationUpdate() {
         @Override
-        public void handleImsRegistered(int imsRadioTech) {
+        public void handleImsRegistered(@NonNull ImsRegistrationAttributes attributes) {
+            int imsRadioTech = attributes.getTransportType();
             if (DBG) {
                 logd("handleImsRegistered: onImsMmTelConnected imsRadioTech="
                         + AccessNetworkConstants.transportTypeToString(imsRadioTech));
@@ -2469,6 +2477,10 @@
             getDefaultPhone().setImsRegistrationState(true);
             mMetrics.writeOnImsConnectionState(mPhoneId, ImsConnectionState.State.CONNECTED, null);
             mImsStats.onImsRegistered(imsRadioTech);
+            mImsNrSaModeHandler.onImsRegistered(
+                    attributes.getRegistrationTechnology(), attributes.getFeatureTags());
+            updateImsRegistrationInfo(REGISTRATION_STATE_REGISTERED,
+                    attributes.getRegistrationTechnology(), SUGGESTED_ACTION_NONE);
         }
 
         @Override
@@ -2487,10 +2499,13 @@
         }
 
         @Override
-        public void handleImsUnregistered(ImsReasonInfo imsReasonInfo) {
+        public void handleImsUnregistered(ImsReasonInfo imsReasonInfo,
+                @RegistrationManager.SuggestedAction int suggestedAction,
+                @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech) {
             if (DBG) {
                 logd("handleImsUnregistered: onImsMmTelDisconnected imsReasonInfo="
-                        + imsReasonInfo);
+                        + imsReasonInfo + ", suggestedAction=" + suggestedAction
+                        + ", disconnectedRadioTech=" + imsRadioTech);
             }
             mRegLocalLog.log("handleImsUnregistered: onImsMmTelDisconnected imsRadioTech="
                     + imsReasonInfo);
@@ -2500,6 +2515,17 @@
             mMetrics.writeOnImsConnectionState(mPhoneId, ImsConnectionState.State.DISCONNECTED,
                     imsReasonInfo);
             mImsStats.onImsUnregistered(imsReasonInfo);
+            mImsNrSaModeHandler.onImsUnregistered(imsRadioTech);
+            mImsRegistrationTech = REGISTRATION_TECH_NONE;
+            int suggestedModemAction = SUGGESTED_ACTION_NONE;
+            if (imsReasonInfo.getCode() == ImsReasonInfo.CODE_REGISTRATION_ERROR) {
+                if ((suggestedAction == SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK)
+                        || (suggestedAction == SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT)) {
+                    suggestedModemAction = suggestedAction;
+                }
+            }
+            updateImsRegistrationInfo(REGISTRATION_STATE_NOT_REGISTERED,
+                    imsRadioTech, suggestedModemAction);
         }
 
         @Override
@@ -2520,44 +2546,24 @@
         int subId = getSubId();
         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
             // Defending b/219080264:
-            // SubscriptionController.setSubscriptionProperty validates input subId
+            // SubscriptionManagerService.setSubscriptionProperty validates input subId
             // so do not proceed if subId invalid. This may be happening because cached
             // IMS callbacks are sent back to telephony after SIM state changed.
             return;
         }
 
-        if (isSubscriptionManagerServiceEnabled()) {
-            SubscriptionInfoInternal subInfo = mSubscriptionManagerService
-                    .getSubscriptionInfoInternal(subId);
-            if (subInfo != null) {
-                phoneNumber = PhoneNumberUtils.formatNumberToE164(phoneNumber,
-                        subInfo.getCountryIso());
-                if (phoneNumber == null) {
-                    return;
-                }
-                mSubscriptionManagerService.setNumberFromIms(subId, phoneNumber);
-            }
-        } else {
-            SubscriptionController subController = SubscriptionController.getInstance();
-            String countryIso = getCountryIso(subController, subId);
-            // Format the number as one more defense to reject garbage values:
-            // phoneNumber will become null.
-            phoneNumber = PhoneNumberUtils.formatNumberToE164(phoneNumber, countryIso);
+        SubscriptionInfoInternal subInfo = mSubscriptionManagerService
+                .getSubscriptionInfoInternal(subId);
+        if (subInfo != null) {
+            phoneNumber = PhoneNumberUtils.formatNumberToE164(phoneNumber,
+                    subInfo.getCountryIso());
             if (phoneNumber == null) {
                 return;
             }
-            subController.setSubscriptionProperty(subId, COLUMN_PHONE_NUMBER_SOURCE_IMS,
-                    phoneNumber);
+            mSubscriptionManagerService.setNumberFromIms(subId, phoneNumber);
         }
     }
 
-    private static String getCountryIso(SubscriptionController subController, int subId) {
-        SubscriptionInfo info = subController.getSubscriptionInfo(subId);
-        String countryIso = info == null ? "" : info.getCountryIso();
-        // info.getCountryIso() may return null
-        return countryIso == null ? "" : countryIso;
-    }
-
     /**
      * Finds the phone number from associated URIs.
      *
@@ -2632,6 +2638,131 @@
         return mLastKnownRoamingState;
     }
 
+    /**
+     * Update IMS registration information to modem.
+     *
+     * @param capabilities indicate MMTEL capability such as VOICE, VIDEO and SMS.
+     */
+    public void updateImsRegistrationInfo(int capabilities) {
+        if (mImsRegistrationState == REGISTRATION_STATE_REGISTERED) {
+            if (mNotifiedRegisteredState && (capabilities == mImsRegistrationCapabilities)) {
+                // Duplicated notification, no change in capabilities.
+                return;
+            }
+
+            mImsRegistrationCapabilities = capabilities;
+            if (capabilities == 0) {
+                // Ignore this as this usually happens just before onUnregistered callback.
+                // We can notify modem when onUnregistered() flow occurs.
+                return;
+            }
+
+            mDefaultPhone.mCi.updateImsRegistrationInfo(mImsRegistrationState,
+                    mImsRegistrationTech, 0, capabilities, null);
+            mNotifiedRegisteredState = true;
+        }
+    }
+
+    /**
+     * Update IMS registration info
+     *
+     * @param regState indicates IMS registration state.
+     * @param imsRadioTech indicates the type of the radio access network where IMS is registered.
+     * @param suggestedAction indicates the suggested action for the radio to perform.
+     */
+    private void updateImsRegistrationInfo(
+            @RegistrationManager.ImsRegistrationState int regState,
+            @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech,
+            @RegistrationManager.SuggestedAction int suggestedAction) {
+
+        if (regState == mImsRegistrationState) {
+            if ((regState == REGISTRATION_STATE_REGISTERED && imsRadioTech == mImsRegistrationTech)
+                    || (regState == REGISTRATION_STATE_NOT_REGISTERED
+                            && suggestedAction == mImsRegistrationSuggestedAction
+                            && imsRadioTech == mImsDeregistrationTech)) {
+                // Filter duplicate notification.
+                return;
+            }
+        }
+
+        if (regState == REGISTRATION_STATE_NOT_REGISTERED) {
+            mDefaultPhone.mCi.updateImsRegistrationInfo(regState,
+                    imsRadioTech, suggestedAction, 0, null);
+        } else if (mImsRegistrationState == REGISTRATION_STATE_REGISTERED) {
+            // This happens when radio tech is changed while in REGISTERED state.
+            if (mImsRegistrationCapabilities > 0) {
+                // Capability has been updated. Notify REGISTRATION_STATE_REGISTERED.
+                mDefaultPhone.mCi.updateImsRegistrationInfo(regState, imsRadioTech, 0,
+                        mImsRegistrationCapabilities, null);
+                mImsRegistrationTech = imsRadioTech;
+                mNotifiedRegisteredState = true;
+                return;
+            }
+        }
+
+        mImsRegistrationState = regState;
+        mImsRegistrationTech = imsRadioTech;
+        mImsRegistrationSuggestedAction = suggestedAction;
+        if (regState == REGISTRATION_STATE_NOT_REGISTERED) {
+            mImsDeregistrationTech = imsRadioTech;
+        } else {
+            mImsDeregistrationTech = REGISTRATION_TECH_NONE;
+        }
+        mImsRegistrationCapabilities = 0;
+        // REGISTRATION_STATE_REGISTERED will be notified when the capability is updated.
+        mNotifiedRegisteredState = false;
+    }
+
+    @Override
+    public void setTerminalBasedCallWaitingStatus(int state) {
+        mCT.setTerminalBasedCallWaitingStatus(state);
+    }
+
+    @Override
+    public void triggerEpsFallback(@MmTelFeature.EpsFallbackReason int reason, Message response) {
+        mDefaultPhone.triggerEpsFallback(reason, response);
+    }
+
+    @Override
+    public void startImsTraffic(int token,
+            @MmTelFeature.ImsTrafficType int trafficType,
+            @AccessNetworkConstants.RadioAccessNetworkType int accessNetworkType,
+            @MmTelFeature.ImsTrafficDirection int trafficDirection, Message response) {
+        mDefaultPhone.startImsTraffic(token, trafficType,
+                accessNetworkType, trafficDirection, response);
+    }
+
+    @Override
+    public void stopImsTraffic(int token, Message response) {
+        mDefaultPhone.stopImsTraffic(token, response);
+    }
+
+    @Override
+    public void registerForConnectionSetupFailure(Handler h, int what, Object obj) {
+        mDefaultPhone.registerForConnectionSetupFailure(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForConnectionSetupFailure(Handler h) {
+        mDefaultPhone.unregisterForConnectionSetupFailure(h);
+    }
+
+    @Override
+    public void triggerImsDeregistration(
+            @ImsRegistrationImplBase.ImsDeregistrationReason int reason) {
+        mCT.triggerImsDeregistration(reason);
+    }
+
+    @Override
+    public void updateImsCallStatus(List<ImsCallInfo> imsCallInfo, Message response) {
+        mDefaultPhone.updateImsCallStatus(imsCallInfo, response);
+    }
+
+    @Override
+    public void triggerNotifyAnbr(int mediaType, int direction, int bitsPerSecond) {
+        mCT.triggerNotifyAnbr(mediaType, direction, bitsPerSecond);
+    }
+
     @Override
     public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
         IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java
index b5c7da1..8a1041d 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneBase.java
@@ -24,11 +24,13 @@
 import android.sysprop.TelephonyProperties;
 import android.telephony.Annotation.DataActivityType;
 import android.telephony.CallQuality;
+import android.telephony.CallState;
 import android.telephony.NetworkScanRequest;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.MediaQualityStatus;
 import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -138,6 +140,10 @@
         mNotifier.notifyCallQualityChanged(this, callQuality, callNetworkType);
     }
 
+    public void onMediaQualityStatusChanged(MediaQualityStatus status) {
+        mNotifier.notifyMediaQualityStatusChanged(this, status);
+    }
+
     @Override
     public ServiceState getServiceState() {
         // FIXME: we may need to provide this when data connectivity is lost
@@ -192,7 +198,43 @@
      */
     public void notifyPreciseCallStateChanged() {
         /* we'd love it if this was package-scoped*/
-        super.notifyPreciseCallStateChangedP();
+        AsyncResult ar = new AsyncResult(null, this, null);
+        mPreciseCallStateRegistrants.notifyRegistrants(ar);
+
+        notifyPreciseCallStateToNotifier();
+    }
+
+    public void notifyPreciseCallStateToNotifier() {
+        ImsPhoneCall ringingCall = (ImsPhoneCall) getRingingCall();
+        ImsPhoneCall foregroundCall = (ImsPhoneCall) getForegroundCall();
+        ImsPhoneCall backgroundCall = (ImsPhoneCall) getBackgroundCall();
+
+        if (ringingCall != null && foregroundCall != null && backgroundCall != null) {
+            //Array for IMS call session ID of RINGING/FOREGROUND/BACKGROUND call
+            String[] imsCallIds = new String[CallState.CALL_CLASSIFICATION_MAX];
+            //Array for IMS call service type of RINGING/FOREGROUND/BACKGROUND call
+            int[] imsCallServiceTypes = new int[CallState.CALL_CLASSIFICATION_MAX];
+            //Array for IMS call type of RINGING/FOREGROUND/BACKGROUND call
+            int[] imsCallTypes = new int[CallState.CALL_CLASSIFICATION_MAX];
+            imsCallIds[CallState.CALL_CLASSIFICATION_RINGING] =
+                    ringingCall.getCallSessionId();
+            imsCallIds[CallState.CALL_CLASSIFICATION_FOREGROUND] =
+                    foregroundCall.getCallSessionId();
+            imsCallIds[CallState.CALL_CLASSIFICATION_BACKGROUND] =
+                    backgroundCall.getCallSessionId();
+            imsCallServiceTypes[CallState.CALL_CLASSIFICATION_RINGING] =
+                    ringingCall.getServiceType();
+            imsCallServiceTypes[CallState.CALL_CLASSIFICATION_FOREGROUND] =
+                    foregroundCall.getServiceType();
+            imsCallServiceTypes[CallState.CALL_CLASSIFICATION_BACKGROUND] =
+                    backgroundCall.getServiceType();
+            imsCallTypes[CallState.CALL_CLASSIFICATION_RINGING] = ringingCall.getCallType();
+            imsCallTypes[CallState.CALL_CLASSIFICATION_FOREGROUND] =
+                    foregroundCall.getCallType();
+            imsCallTypes[CallState.CALL_CLASSIFICATION_BACKGROUND] =
+                    backgroundCall.getCallType();
+            mNotifier.notifyPreciseCallState(this, imsCallIds, imsCallServiceTypes, imsCallTypes);
+        }
     }
 
     public void notifyDisconnect(Connection cn) {
@@ -300,6 +342,11 @@
     }
 
     @Override
+    public int getImeiType() {
+        return Phone.IMEI_TYPE_UNKNOWN;
+    }
+
+    @Override
     public String getEsn() {
         Rlog.e(LOG_TAG, "[VoltePhone] getEsn() is a CDMA method");
         return "0";
@@ -530,4 +577,14 @@
             notifyPhoneStateChanged();
         }
     }
+
+    @Override
+    public int getTerminalBasedCallWaitingState(boolean forCsOnly) {
+        return getDefaultPhone().getTerminalBasedCallWaitingState(forCsOnly);
+    }
+
+    @Override
+    public void setTerminalBasedCallWaitingSupported(boolean supported) {
+        getDefaultPhone().setTerminalBasedCallWaitingSupported(supported);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCall.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCall.java
index 98cc441..eaa6ab6 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCall.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCall.java
@@ -19,6 +19,8 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.telephony.DisconnectCause;
+import android.telephony.ims.ImsCallProfile;
+import android.telephony.ims.ImsCallSession;
 import android.telephony.ims.ImsStreamMediaProfile;
 import android.util.Log;
 
@@ -327,6 +329,41 @@
         return (connection == null) ? null : connection.getImsCall();
     }
 
+    /**
+     * Retrieves the {@link ImsCallSession#getCallId()} for the current {@link ImsPhoneCall}.
+     */
+    @VisibleForTesting
+    public String getCallSessionId() {
+        return ((getImsCall() == null) ? null : getImsCall().getSession()) == null
+                ? null : getImsCall().getSession().getCallId();
+    }
+
+    /**
+     * Retrieves the service type in {@link ImsCallProfile} for the current {@link ImsPhoneCall}.
+     */
+    @VisibleForTesting
+    public int getServiceType() {
+        if (getFirstConnection() == null) {
+            return ImsCallProfile.SERVICE_TYPE_NONE;
+        } else {
+            return getFirstConnection().isEmergencyCall()
+                    ? ImsCallProfile.SERVICE_TYPE_EMERGENCY : ImsCallProfile.SERVICE_TYPE_NORMAL;
+        }
+    }
+
+    /**
+     * Retrieves the call type in {@link ImsCallProfile} for the current {@link ImsPhoneCall}.
+     */
+    @VisibleForTesting
+    public int getCallType() {
+        if (getImsCall() == null) {
+            return ImsCallProfile.CALL_TYPE_NONE;
+        } else {
+            return getImsCall().isVideoCall()
+                    ? ImsCallProfile.CALL_TYPE_VT : ImsCallProfile.CALL_TYPE_VOICE;
+        }
+    }
+
     /*package*/ static boolean isLocalTone(ImsCall imsCall) {
         if ((imsCall == null) || (imsCall.getCallProfile() == null)
                 || (imsCall.getCallProfile().mMediaProfile == null)) {
@@ -432,6 +469,15 @@
         return mIsRingbackTonePlaying;
     }
 
+    public void maybeClearRemotelyHeldStatus() {
+        for (Connection conn : getConnections()) {
+            ImsPhoneConnection c = (ImsPhoneConnection) conn;
+            if (c.isHeldByRemote()) {
+                c.setRemotelyUnheld();
+            }
+        }
+    }
+
     private void takeOver(ImsPhoneCall that) {
         copyConnectionFrom(that);
         mState = that.mState;
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
index 7b19bc9..c3ee0f6 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -16,12 +16,32 @@
 
 package com.android.internal.telephony.imsphone;
 
+import static android.telephony.CarrierConfigManager.ImsVoice.ALERTING_SRVCC_SUPPORT;
+import static android.telephony.CarrierConfigManager.ImsVoice.BASIC_SRVCC_SUPPORT;
+import static android.telephony.CarrierConfigManager.ImsVoice.MIDCALL_SRVCC_SUPPORT;
+import static android.telephony.CarrierConfigManager.ImsVoice.PREALERTING_SRVCC_SUPPORT;
 import static android.telephony.CarrierConfigManager.USSD_OVER_CS_PREFERRED;
 import static android.telephony.CarrierConfigManager.USSD_OVER_IMS_ONLY;
+import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_ACTIVE;
+import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_ALERTING;
+import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_DIALING;
+import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_HOLDING;
+import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_INCOMING;
+import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_INCOMING_SETUP;
+import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_WAITING;
+import static android.telephony.ims.ImsService.CAPABILITY_TERMINAL_BASED_CALL_WAITING;
+import static android.telephony.ims.feature.ConnectionFailureInfo.REASON_UNSPECIFIED;
+import static android.telephony.ims.feature.MmTelFeature.ImsTrafficSessionCallbackWrapper.INVALID_TOKEN;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+import static com.android.internal.telephony.CallWaitingController.TERMINAL_BASED_ACTIVATED;
+import static com.android.internal.telephony.CallWaitingController.TERMINAL_BASED_NOT_SUPPORTED;
+import static com.android.internal.telephony.CommandsInterface.IMS_MMTEL_CAPABILITY_SMS;
+import static com.android.internal.telephony.CommandsInterface.IMS_MMTEL_CAPABILITY_VIDEO;
+import static com.android.internal.telephony.CommandsInterface.IMS_MMTEL_CAPABILITY_VOICE;
 import static com.android.internal.telephony.Phone.CS_FALLBACK;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.usage.NetworkStatsManager;
@@ -56,6 +76,7 @@
 import android.telecom.Connection.VideoProvider;
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
+import android.telephony.AccessNetworkConstants;
 import android.telephony.CallQuality;
 import android.telephony.CarrierConfigManager;
 import android.telephony.DisconnectCause;
@@ -74,9 +95,16 @@
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ImsStreamMediaProfile;
 import android.telephony.ims.ImsSuppServiceNotification;
+import android.telephony.ims.MediaQualityStatus;
+import android.telephony.ims.MediaThreshold;
 import android.telephony.ims.ProvisioningManager;
 import android.telephony.ims.RtpHeaderExtension;
 import android.telephony.ims.RtpHeaderExtensionType;
+import android.telephony.ims.SrvccCall;
+import android.telephony.ims.aidl.IImsCallSessionListener;
+import android.telephony.ims.aidl.IImsTrafficSessionCallback;
+import android.telephony.ims.aidl.ISrvccStartedCallback;
+import android.telephony.ims.feature.ConnectionFailureInfo;
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
@@ -114,10 +142,12 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.ServiceStateTracker;
-import com.android.internal.telephony.SubscriptionController;
+import com.android.internal.telephony.SrvccConnection;
 import com.android.internal.telephony.d2d.RtpTransport;
 import com.android.internal.telephony.data.DataSettingsManager;
+import com.android.internal.telephony.domainselection.DomainSelectionResolver;
 import com.android.internal.telephony.emergency.EmergencyNumberTracker;
+import com.android.internal.telephony.emergency.EmergencyStateTracker;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
 import com.android.internal.telephony.imsphone.ImsPhone.ImsDialArgs;
 import com.android.internal.telephony.metrics.CallQualityMetrics;
@@ -133,8 +163,10 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
@@ -145,11 +177,14 @@
 import java.util.concurrent.CompletionException;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 /**
  * {@hide}
@@ -180,6 +215,20 @@
         SharedPreferences getDefaultSharedPreferences(Context context);
     }
 
+    private static class ImsTrafficSession {
+        private final @MmTelFeature.ImsTrafficType int mTrafficType;
+        private final @MmTelFeature.ImsTrafficDirection int mTrafficDirection;
+        private final @NonNull IImsTrafficSessionCallback mCallback;
+
+        ImsTrafficSession(@MmTelFeature.ImsTrafficType int trafficType,
+                @MmTelFeature.ImsTrafficDirection int trafficDirection,
+                @NonNull IImsTrafficSessionCallback callback) {
+            mTrafficType = trafficType;
+            mTrafficDirection = trafficDirection;
+            mCallback = callback;
+        }
+    }
+
     private static final boolean DBG = true;
 
     // When true, dumps the state of ImsPhoneCallTracker after changes to foreground and background
@@ -210,16 +259,18 @@
     private final MmTelFeatureListener mMmTelFeatureListener = new MmTelFeatureListener();
     private class MmTelFeatureListener extends MmTelFeature.Listener {
 
-        private void processIncomingCall(IImsCallSession c, Bundle extras) {
+        private IImsCallSessionListener processIncomingCall(@NonNull IImsCallSession c,
+                @Nullable String callId, @Nullable Bundle extras) {
             if (DBG) log("processIncomingCall: incoming call intent");
 
             if (extras == null) extras = new Bundle();
-            if (mImsManager == null) return;
+            if (mImsManager == null) return null;
 
             try {
+                IImsCallSessionListener iimsCallSessionListener;
                 // Network initiated USSD will be treated by mImsUssdListener
                 boolean isUssd = extras.getBoolean(MmTelFeature.EXTRA_IS_USSD, false);
-                // For compatibility purposes with older vendor implmentations.
+                // For compatibility purposes with older vendor implementations.
                 isUssd |= extras.getBoolean(ImsManager.EXTRA_USSD, false);
                 if (isUssd) {
                     if (DBG) log("processIncomingCall: USSD");
@@ -228,11 +279,14 @@
                     if (mUssdSession != null) {
                         mUssdSession.accept(ImsCallProfile.CALL_TYPE_VOICE);
                     }
-                    return;
+                    if (callId != null) mUssdSession.getCallSession().setCallId(callId);
+                    iimsCallSessionListener = (IImsCallSessionListener) mUssdSession
+                            .getCallSession().getIImsCallSessionListenerProxy();
+                    return iimsCallSessionListener;
                 }
 
                 boolean isUnknown = extras.getBoolean(MmTelFeature.EXTRA_IS_UNKNOWN_CALL, false);
-                // For compatibility purposes with older vendor implmentations.
+                // For compatibility purposes with older vendor implementations.
                 isUnknown |= extras.getBoolean(ImsManager.EXTRA_IS_UNKNOWN_CALL, false);
                 if (DBG) {
                     log("processIncomingCall: isUnknown = " + isUnknown
@@ -242,6 +296,9 @@
 
                 // Normal MT/Unknown call
                 ImsCall imsCall = mImsManager.takeCall(c, mImsCallListener);
+                if (callId != null) imsCall.getCallSession().setCallId(callId);
+                iimsCallSessionListener = (IImsCallSessionListener) imsCall
+                        .getCallSession().getIImsCallSessionListenerProxy();
                 ImsPhoneConnection conn = new ImsPhoneConnection(mPhone, imsCall,
                         ImsPhoneCallTracker.this,
                         (isUnknown ? mForegroundCall : mRingingCall), isUnknown);
@@ -264,13 +321,13 @@
                 if ((c != null) && (c.getCallProfile() != null)
                         && (c.getCallProfile().getCallExtras() != null)
                         && (c.getCallProfile().getCallExtras()
-                          .containsKey(ImsCallProfile.EXTRA_CALL_DISCONNECT_CAUSE))) {
+                        .containsKey(ImsCallProfile.EXTRA_CALL_DISCONNECT_CAUSE))) {
                     String error = c.getCallProfile()
                             .getCallExtra(ImsCallProfile.EXTRA_CALL_DISCONNECT_CAUSE, null);
                     if (error != null) {
                         try {
                             int cause = getDisconnectCauseFromReasonInfo(
-                                        new ImsReasonInfo(Integer.parseInt(error), 0, null),
+                                    new ImsReasonInfo(Integer.parseInt(error), 0, null),
                                     conn.getState());
                             if (cause == DisconnectCause.INCOMING_AUTO_REJECTED) {
                                 conn.setDisconnectCause(cause);
@@ -315,18 +372,20 @@
 
                 updatePhoneState();
                 mPhone.notifyPreciseCallStateChanged();
+                mImsCallInfoTracker.addImsCallStatus(conn);
+                return iimsCallSessionListener;
             } catch (ImsException | RemoteException e) {
                 loge("processIncomingCall: exception " + e);
                 mOperationLocalLog.log("onIncomingCall: exception processing: "  + e);
+                return null;
             }
         }
 
         @Override
-        public void onIncomingCall(IImsCallSession c, Bundle extras) {
-            // we want to ensure we block this binder thread until incoming call setup completes
-            // as to avoid race conditions where the ImsService tries to update the state of the
-            // call before the listeners have been attached.
-            executeAndWait(()-> processIncomingCall(c, extras));
+        @Nullable
+        public IImsCallSessionListener onIncomingCall(
+                @NonNull IImsCallSession c, @Nullable String callId, @Nullable Bundle extras) {
+            return executeAndWaitForReturn(()-> processIncomingCall(c, callId, extras));
         }
 
         @Override
@@ -341,6 +400,102 @@
             }, mExecutor);
         }
 
+        @Override
+        public void onAudioModeIsVoipChanged(int imsAudioHandler) {
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                ImsCall imsCall = null;
+                if (mForegroundCall.hasConnections()) {
+                    imsCall = mForegroundCall.getImsCall();
+                } else if (mBackgroundCall.hasConnections()) {
+                    imsCall = mBackgroundCall.getImsCall();
+                } else if (mRingingCall.hasConnections()) {
+                    imsCall = mRingingCall.getImsCall();
+                } else if (mHandoverCall.hasConnections()) {
+                    imsCall = mHandoverCall.getImsCall();
+                } else {
+                    Rlog.e(LOG_TAG, "onAudioModeIsVoipChanged: no Call");
+                }
+
+                if (imsCall != null) {
+                    ImsPhoneConnection conn = findConnection(imsCall);
+                    if (conn != null) {
+                        conn.onAudioModeIsVoipChanged(imsAudioHandler);
+                    }
+                } else {
+                    Rlog.e(LOG_TAG, "onAudioModeIsVoipChanged: no ImsCall");
+                }
+            }, mExecutor);
+        }
+
+        @Override
+        public void onTriggerEpsFallback(@MmTelFeature.EpsFallbackReason int reason) {
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (DBG) log("onTriggerEpsFallback reason=" + reason);
+                mPhone.triggerEpsFallback(reason, null);
+            }, mExecutor);
+        }
+
+        @Override
+        public void onStartImsTrafficSession(int token,
+                @MmTelFeature.ImsTrafficType int trafficType,
+                @AccessNetworkConstants.RadioAccessNetworkType int accessNetworkType,
+                @MmTelFeature.ImsTrafficDirection int trafficDirection,
+                IImsTrafficSessionCallback callback) {
+            registerImsTrafficSession(token, trafficType, trafficDirection, callback);
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (DBG) {
+                    log("onStartImsTrafficSession token=" + token + ", traffic=" + trafficType
+                            + ", networkType=" + accessNetworkType
+                            + ", direction=" + trafficDirection);
+                }
+                mPhone.startImsTraffic(token, trafficType, accessNetworkType, trafficDirection,
+                        obtainMessage(EVENT_START_IMS_TRAFFIC_DONE, callback));
+            }, mExecutor);
+        }
+
+        @Override
+        public void onModifyImsTrafficSession(int token,
+                @AccessNetworkConstants.RadioAccessNetworkType int accessNetworkType) {
+            ImsTrafficSession session = getImsTrafficSession(token);
+            if (session == null) {
+                loge("onModifyImsTrafficSession unknown session, token=" + token);
+                return;
+            }
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (DBG) {
+                    log("onModifyImsTrafficSession token=" + token
+                            + ", networkType=" + accessNetworkType);
+                }
+                mPhone.startImsTraffic(token, session.mTrafficType,
+                        accessNetworkType, session.mTrafficDirection,
+                        obtainMessage(EVENT_START_IMS_TRAFFIC_DONE, session.mCallback));
+            }, mExecutor);
+        }
+
+        @Override
+        public void onStopImsTrafficSession(int token) {
+            unregisterImsTrafficSession(token);
+            if (token == INVALID_TOKEN) return;
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (DBG) {
+                    log("onStopImsTrafficSession token=" + token);
+                }
+                mPhone.stopImsTraffic(token, null);
+            }, mExecutor);
+        }
+
+        @Override
+        public void onMediaQualityStatusChanged(MediaQualityStatus status) {
+            TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                if (mPhone != null && mPhone.mDefaultPhone != null) {
+                    if (DBG) log("onMediaQualityStatusChanged " + status);
+                    mPhone.onMediaQualityStatusChanged(status);
+                } else {
+                    loge("onMediaQualityStatusChanged: null phone");
+                }
+            }, mExecutor);
+        }
+
         /**
          * Schedule the given Runnable on mExecutor and block this thread until it finishes.
          * @param r The Runnable to run.
@@ -353,6 +508,24 @@
                 logw("Binder - exception: " + e.getMessage());
             }
         }
+
+        /**
+         * Schedule the given Runnable on mExecutor and block this thread until it finishes.
+         * @param r The Runnable to run.
+         */
+        private <T> T executeAndWaitForReturn(Supplier<T> r) {
+
+            CompletableFuture<T> future = CompletableFuture.supplyAsync(
+                    () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor);
+
+            try {
+                return future.get();
+            } catch (ExecutionException | InterruptedException e) {
+                Log.w(LOG_TAG, "ImsPhoneCallTracker : executeAndWaitForReturn exception: "
+                        + e.getMessage());
+                return null;
+            }
+        }
     }
 
     /**
@@ -488,6 +661,9 @@
     private static final int EVENT_ANSWER_WAITING_CALL = 30;
     private static final int EVENT_RESUME_NOW_FOREGROUND_CALL = 31;
     private static final int EVENT_REDIAL_WITHOUT_RTT = 32;
+    private static final int EVENT_START_IMS_TRAFFIC_DONE = 33;
+    private static final int EVENT_CONNECTION_SETUP_FAILURE = 34;
+    private static final int EVENT_NEW_ACTIVE_CALL_STARTED = 35;
 
     private static final int TIMEOUT_HANGUP_PENDINGMO = 500;
 
@@ -559,6 +735,13 @@
         }
     }
 
+    private class SrvccStartedCallback extends ISrvccStartedCallback.Stub {
+        @Override
+        public void onSrvccCallNotified(List<SrvccCall> profiles) {
+            handleSrvccConnectionInfo(profiles);
+        }
+    }
+
     private volatile NetworkStats mVtDataUsageSnapshot = null;
     private volatile NetworkStats mVtDataUsageUidSnapshot = null;
     private final VtDataUsageProvider mVtDataUsageProvider = new VtDataUsageProvider();
@@ -613,14 +796,22 @@
     private boolean mSupportCepOnPeer = true;
     private boolean mSupportD2DUsingRtp = false;
     private boolean mSupportSdpForRtpHeaderExtensions = false;
+    private int mThresholdRtpPacketLoss;
+    private int mThresholdRtpJitter;
+    private long mThresholdRtpInactivityTime;
+    private final List<Integer> mSrvccTypeSupported = new ArrayList<>();
+    private final SrvccStartedCallback mSrvccStartedCallback = new SrvccStartedCallback();
     // Tracks the state of our background/foreground calls while a call hold/swap operation is
     // in progress. Values listed above.
     private HoldSwapState mHoldSwitchingState = HoldSwapState.INACTIVE;
+    private MediaThreshold mMediaThreshold;
 
     private String mLastDialString = null;
     private ImsDialArgs mLastDialArgs = null;
     private Executor mExecutor = Runnable::run;
 
+    private final ImsCallInfoTracker mImsCallInfoTracker;
+
     /**
      * Listeners to changes in the phone state.  Intended for use by other interested IMS components
      * without the need to register a full blown {@link android.telephony.PhoneStateListener}.
@@ -992,6 +1183,9 @@
     // Used for important operational related events for logging.
     private final LocalLog mOperationLocalLog = new LocalLog(64);
 
+    private final ConcurrentHashMap<Integer, ImsTrafficSession> mImsTrafficSessions =
+            new ConcurrentHashMap<>();
+
     /**
      * Container to ease passing around a tuple of two objects. This object provides a sensible
      * implementation of equals(), returning true/false using equals() for one object (Integer)
@@ -1040,6 +1234,7 @@
             return (first == null ? 0 : first.hashCode());
         }
     }
+
     //***** Events
 
 
@@ -1111,10 +1306,15 @@
                             postDelayed(mConnectorRunnable, CONNECTOR_RETRY_DELAY_MS);
                         }
                         stopListeningForCalls();
+                        stopAllImsTrafficTypes();
                     }
                 }, executor);
         // It can take some time for ITelephony to get published, so defer connecting.
         post(mConnectorRunnable);
+
+        mImsCallInfoTracker = new ImsCallInfoTracker(phone);
+
+        mPhone.registerForConnectionSetupFailure(this, EVENT_CONNECTION_SETUP_FAILURE, null);
     }
 
     /**
@@ -1191,6 +1391,8 @@
         // For compatibility with apps that still use deprecated intent
         sendImsServiceStateIntent(ImsManager.ACTION_IMS_SERVICE_UP);
         mCurrentlyConnectedSubId = Optional.of(subId);
+
+        initializeTerminalBasedCallWaiting();
     }
 
     /**
@@ -1251,6 +1453,7 @@
             mUtInterface = null;
         }
         mCurrentlyConnectedSubId = Optional.empty();
+        mMediaThreshold = null;
         resetImsCapabilities();
         hangupAllOrphanedConnections(DisconnectCause.LOST_SIGNAL);
         // For compatibility with apps that still use deprecated intent
@@ -1291,6 +1494,7 @@
         // above. Remove all references to it.
         mPendingMO = null;
         updatePhoneState();
+        mImsCallInfoTracker.clearAllOrphanedConnections();
     }
 
     /**
@@ -1335,6 +1539,8 @@
                 (NetworkStatsManager) mPhone.getContext().getSystemService(
                         Context.NETWORK_STATS_SERVICE);
         statsManager.unregisterNetworkStatsProvider(mVtDataUsageProvider);
+
+        mPhone.unregisterForConnectionSetupFailure(this);
     }
 
     @Override
@@ -1526,7 +1732,7 @@
             mLastDialString = dialString;
             mLastDialArgs = dialArgs;
             mPendingMO = new ImsPhoneConnection(mPhone, dialString, this, mForegroundCall,
-                    isEmergencyNumber, isWpsCall);
+                    isEmergencyNumber, isWpsCall, dialArgs);
             mOperationLocalLog.log("dial requested. connId=" + System.identityHashCode(mPendingMO));
             if (isEmergencyNumber && dialArgs != null && dialArgs.intentExtras != null) {
                 Rlog.i(LOG_TAG, "dial ims emergency dialer: " + dialArgs.intentExtras.getBoolean(
@@ -1543,9 +1749,22 @@
         addConnection(mPendingMO);
 
         if (!holdBeforeDial) {
+            // In Ecm mode, if another emergency call is dialed, Ecm mode will not exit.
             if ((!isPhoneInEcmMode) || (isPhoneInEcmMode && isEmergencyNumber)) {
                 dialInternal(mPendingMO, clirMode, videoState, dialArgs.retryCallFailCause,
                         dialArgs.retryCallFailNetworkType, dialArgs.intentExtras);
+            } else if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
+                final int finalClirMode = clirMode;
+                final int finalVideoState = videoState;
+                Runnable onComplete = new Runnable() {
+                    @Override
+                    public void run() {
+                        dialInternal(mPendingMO, finalClirMode, finalVideoState,
+                                dialArgs.retryCallFailCause, dialArgs.retryCallFailNetworkType,
+                                dialArgs.intentExtras);
+                    }
+                };
+                EmergencyStateTracker.getInstance().exitEmergencyCallbackMode(onComplete);
             } else {
                 try {
                     getEcbmInterface().exitEmergencyCallbackMode();
@@ -1637,20 +1856,12 @@
         // Check for changes due to carrier config.
         maybeConfigureRtpHeaderExtensions();
 
-        if (mPhone.isSubscriptionManagerServiceEnabled()) {
-            SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
-                    .getSubscriptionInfoInternal(subId);
-            if (subInfo == null || !subInfo.isActive()) {
-                loge("updateCarrierConfiguration: skipping notification to ImsService, non"
-                        + "active subId = " + subId);
-                return;
-            }
-        } else {
-            if (!SubscriptionController.getInstance().isActiveSubId(subId)) {
-                loge("updateCarrierConfiguration: skipping notification to ImsService, non"
-                        + "active subId = " + subId);
-                return;
-            }
+        SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
+                .getSubscriptionInfoInternal(subId);
+        if (subInfo == null || !subInfo.isActive()) {
+            loge("updateCarrierConfiguration: skipping notification to ImsService, non"
+                    + "active subId = " + subId);
+            return;
         }
 
         Phone defaultPhone = getPhone().getDefaultPhone();
@@ -1677,6 +1888,8 @@
         logi("updateCarrierConfiguration: Updating ImsService configs.");
         mCarrierConfigLoadedForSubscription = true;
         updateImsServiceConfig();
+        updateMediaThreshold(
+                mThresholdRtpPacketLoss, mThresholdRtpJitter, mThresholdRtpInactivityTime);
     }
 
     /**
@@ -1725,6 +1938,13 @@
         mSupportSdpForRtpHeaderExtensions = carrierConfig.getBoolean(
                 CarrierConfigManager
                         .KEY_SUPPORTS_SDP_NEGOTIATION_OF_D2D_RTP_HEADER_EXTENSIONS_BOOL);
+        mThresholdRtpPacketLoss = carrierConfig.getInt(
+                CarrierConfigManager.ImsVoice.KEY_VOICE_RTP_PACKET_LOSS_RATE_THRESHOLD_INT);
+        mThresholdRtpInactivityTime = carrierConfig.getLong(
+                CarrierConfigManager.ImsVoice
+                        .KEY_VOICE_RTP_INACTIVITY_TIME_THRESHOLD_MILLIS_LONG);
+        mThresholdRtpJitter = carrierConfig.getInt(
+                CarrierConfigManager.ImsVoice.KEY_VOICE_RTP_JITTER_THRESHOLD_MILLIS_INT);
 
         if (mPhone.getContext().getResources().getBoolean(
                 com.android.internal.R.bool.config_allow_ussd_over_ims)) {
@@ -1771,6 +1991,41 @@
         } else {
             log("No carrier ImsReasonInfo mappings defined.");
         }
+
+        mSrvccTypeSupported.clear();
+        int[] srvccType =
+                carrierConfig.getIntArray(CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY);
+        if (srvccType != null && srvccType.length > 0) {
+            mSrvccTypeSupported.addAll(
+                    Arrays.stream(srvccType).boxed().collect(Collectors.toList()));
+        }
+    }
+
+    private void updateMediaThreshold(
+            int thresholdPacketLoss, int thresholdJitter, long thresholdInactivityTime) {
+        if (!MediaThreshold.isValidRtpInactivityTimeMillis(thresholdInactivityTime)
+                && !MediaThreshold.isValidJitterMillis(thresholdJitter)
+                && !MediaThreshold.isValidRtpPacketLossRate(thresholdPacketLoss)) {
+            logi("There is no valid RTP threshold value");
+            return;
+        }
+        int[] thPacketLosses = {thresholdPacketLoss};
+        long[] thInactivityTimesMillis = {thresholdInactivityTime};
+        int[] thJitters = {thresholdJitter};
+        MediaThreshold threshold = new MediaThreshold.Builder()
+                .setThresholdsRtpPacketLossRate(thPacketLosses)
+                .setThresholdsRtpInactivityTimeMillis(thInactivityTimesMillis)
+                .setThresholdsRtpJitterMillis(thJitters).build();
+        if (mMediaThreshold == null || !mMediaThreshold.equals(threshold)) {
+            logi("setMediaThreshold :" + threshold);
+            try {
+                mImsManager.setMediaThreshold(MediaQualityStatus.MEDIA_SESSION_TYPE_AUDIO,
+                        threshold);
+                mMediaThreshold = threshold;
+            } catch (ImsException e) {
+                loge("setMediaThreshold Failed: " + e);
+            }
+        }
     }
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -1873,6 +2128,11 @@
                     conn.setPulledDialogId(dialogId);
                 }
 
+                if (intentExtras.containsKey(ImsCallProfile.EXTRA_CALL_RAT_TYPE)) {
+                    logi("dialInternal containing EXTRA_CALL_RAT_TYPE, "
+                            +  intentExtras.getString(ImsCallProfile.EXTRA_CALL_RAT_TYPE));
+                }
+
                 // Pack the OEM-specific call extras.
                 profile.mCallExtras.putBundle(ImsCallProfile.EXTRA_OEM_EXTRAS, intentExtras);
 
@@ -1894,6 +2154,7 @@
             setVideoCallProvider(conn, imsCall);
             conn.setAllowAddCallDuringVideoCall(mAllowAddCallDuringVideoCall);
             conn.setAllowHoldingVideoCall(mAllowHoldingVideoCall);
+            mImsCallInfoTracker.addImsCallStatus(conn);
         } catch (ImsException e) {
             loge("dialInternal : " + e);
             mOperationLocalLog.log("dialInternal exception: " + e);
@@ -2615,6 +2876,7 @@
                 + System.identityHashCode(conn));
 
         call.onHangupLocal();
+        mImsCallInfoTracker.updateImsCallStatus(conn);
 
         try {
             if (imsCall != null) {
@@ -2815,14 +3077,26 @@
             conn.maybeChangeRingbackState();
 
             maybeSetVideoCallProvider(conn, imsCall);
+            // IMS call profile might be changed while call state is maintained. In this case, it's
+            // required to notify to CallAttributesListener.
+            // Since call state is not changed, TelephonyRegistry will not notify to
+            // PreciseCallStateListener.
+            mPhone.notifyPreciseCallStateToNotifier();
             return;
         }
 
         // Do not log operations that do not change the state
         mOperationLocalLog.log("processCallStateChange: state=" + state + " cause=" + cause
                 + " connId=" + System.identityHashCode(conn));
-
+        boolean noActiveCall = false;
+        if (mForegroundCall.getState() != ImsPhoneCall.State.ACTIVE
+                && mBackgroundCall.getState() != ImsPhoneCall.State.ACTIVE) {
+            noActiveCall = true;
+        }
         changed = conn.update(imsCall, state);
+        if (noActiveCall && changed && state == ImsPhoneCall.State.ACTIVE) {
+            sendMessage(obtainMessage(EVENT_NEW_ACTIVE_CALL_STARTED));
+        }
         if (state == ImsPhoneCall.State.DISCONNECTED) {
             changed = conn.onDisconnect(cause) || changed;
             //detach the disconnected connections
@@ -2852,6 +3126,7 @@
         }
 
         if (changed) {
+            mImsCallInfoTracker.updateImsCallStatus(conn);
             if (conn.getCall() == mHandoverCall) return;
             updatePhoneState();
             mPhone.notifyPreciseCallStateChanged();
@@ -2883,7 +3158,7 @@
     @VisibleForTesting
     public void addReasonCodeRemapping(Integer fromCode, String message, Integer toCode) {
         if (message != null) {
-            message = message.toLowerCase();
+            message = message.toLowerCase(Locale.ROOT);
         }
         mImsReasonCodeMap.put(new ImsReasonInfoKeyPair(fromCode, message), toCode);
     }
@@ -2904,7 +3179,7 @@
         if (reason == null) {
             reason = "";
         } else {
-            reason = reason.toLowerCase();
+            reason = reason.toLowerCase(Locale.ROOT);
         }
         log("maybeRemapReasonCode : fromCode = " + reasonInfo.getCode() + " ; message = "
                 + reason);
@@ -3114,6 +3389,13 @@
                 break;
 
             case ImsReasonInfo.CODE_SIP_BAD_REQUEST:
+                // Auto-missed/rejected calls can sometimes use this reason cause, but if we see it
+                // for outgoing calls it is just a server error.
+                if (callState == Call.State.DIALING || callState == Call.State.ALERTING) {
+                    return DisconnectCause.SERVER_ERROR;
+                } else {
+                    return DisconnectCause.INCOMING_AUTO_REJECTED;
+                }
             case ImsReasonInfo.CODE_REJECT_CALL_ON_OTHER_SUB:
             case ImsReasonInfo.CODE_REJECT_ONGOING_E911_CALL:
             case ImsReasonInfo.CODE_REJECT_ONGOING_CALL_SETUP:
@@ -3266,6 +3548,28 @@
                                     reasonInfo.mExtraCode,
                                     reasonInfo.mExtraMessage));
 
+            if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
+                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) {
+                    logi("onCallStartFailed eccCategory=" + eccCategory);
+                    if (reasonInfo.getCode() == ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL
+                            || reasonInfo.getExtraCode()
+                                    == ImsReasonInfo.EXTRA_CODE_CALL_RETRY_EMERGENCY) {
+                        conn.setNonDetectableEmergencyCallInfo(eccCategory);
+                    }
+                    conn.setImsReasonInfo(reasonInfo);
+                    sendCallStartFailedDisconnect(imsCall, reasonInfo);
+                    mMetrics.writeOnImsCallStartFailed(mPhone.getPhoneId(),
+                            imsCall.getCallSession(), reasonInfo);
+                    return;
+                }
+            }
+
             if (mPendingMO != null) {
                 // To initiate dialing circuit-switched call
                 if (reasonInfo.getCode() == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED
@@ -3273,6 +3577,7 @@
                         && isForegroundHigherPriority()) {
                     mForegroundCall.detach(mPendingMO);
                     removeConnection(mPendingMO);
+                    mImsCallInfoTracker.updateImsCallStatus(mPendingMO);
                     mPendingMO.finalize();
                     mPendingMO = null;
                     // if we need to perform CSFB of call, hang up any background call
@@ -3304,6 +3609,7 @@
                 if (conn != null) {
                     mForegroundCall.detach(conn);
                     removeConnection(conn);
+                    mImsCallInfoTracker.updateImsCallStatus(conn);
                 }
                 updatePhoneState();
                 mPhone.initiateSilentRedial(reasonInfo.getExtraCode() ==
@@ -3393,6 +3699,12 @@
                 cause = DisconnectCause.IMS_MERGED_SUCCESSFULLY;
             }
 
+            // Ensure the background call is correctly marked as MERGE_COMPLETE before it is
+            // disconnected as part of the IMS merge conference process:
+            if (cause == DisconnectCause.IMS_MERGED_SUCCESSFULLY && conn != null) {
+                conn.onConnectionEvent(android.telecom.Connection.EVENT_MERGE_COMPLETE, null);
+            }
+
             EmergencyNumberTracker emergencyNumberTracker = null;
             EmergencyNumber num = null;
 
@@ -3423,6 +3735,18 @@
             }
 
             if (reasonInfo.getCode() == ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL
+                    && DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
+                if (conn != null) {
+                    int eccCategory = EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED;
+                    if (imsCall != null && imsCall.getCallProfile() != null) {
+                        eccCategory = imsCall.getCallProfile().getEmergencyServiceCategories();
+                        logi("onCallTerminated eccCategory=" + eccCategory);
+                    }
+                    conn.setNonDetectableEmergencyCallInfo(eccCategory);
+                }
+                processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED, cause);
+                return;
+            } else if (reasonInfo.getCode() == ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL
                     && mAutoRetryFailedWifiEmergencyCall) {
                 Pair<ImsCall, ImsReasonInfo> callInfo = new Pair<>(imsCall, reasonInfo);
                 mPhone.getDefaultPhone().mCi.registerForOn(ImsPhoneCallTracker.this,
@@ -3722,7 +4046,8 @@
                     mPhone.stopOnHoldTone(conn);
                     mOnHoldToneStarted = false;
                 }
-                conn.onConnectionEvent(android.telecom.Connection.EVENT_CALL_REMOTELY_UNHELD, null);
+                conn.setRemotelyUnheld();
+                mImsCallInfoTracker.updateImsCallStatus(conn, false, true);
             }
 
             boolean useVideoPauseWorkaround = mPhone.getContext().getResources().getBoolean(
@@ -4132,6 +4457,28 @@
                 conn.receivedRtpHeaderExtensions(rtpHeaderExtensionData);
             }
         }
+
+        /**
+         * Access Network Bitrate Recommendation Query (ANBRQ), see 3GPP TS 26.114.
+         * This API triggers radio to send ANBRQ message to the access network to query the desired
+         * bitrate.
+         *
+         * @param imsCall The ImsCall the data was received on.
+         * @param mediaType MediaType is used to identify media stream such as audio or video.
+         * @param direction Direction of this packet stream (e.g. uplink or downlink).
+         * @param bitsPerSecond This value is the bitrate requested by the other party UE through
+         *        RTP CMR, RTCPAPP or TMMBR, and ImsStack converts this value to the MAC bitrate
+         *        (defined in TS36.321, range: 0 ~ 8000 kbit/s).
+         */
+        @Override
+        public void onCallSessionSendAnbrQuery(ImsCall imsCall, int mediaType, int direction,
+                int bitsPerSecond) {
+            if (DBG) {
+                log("onCallSessionSendAnbrQuery mediaType=" + mediaType + ", direction="
+                    + direction + ", bitPerSecond=" + bitsPerSecond);
+            }
+            handleSendAnbrQuery(mediaType, direction, bitsPerSecond);
+        }
     };
 
     /**
@@ -4269,7 +4616,8 @@
             configChangedIntent.putExtra(ImsConfig.EXTRA_CHANGED_ITEM, item);
             configChangedIntent.putExtra(ImsConfig.EXTRA_NEW_VALUE, value);
             if (mPhone != null && mPhone.getContext() != null) {
-                mPhone.getContext().sendBroadcast(configChangedIntent);
+                mPhone.getContext().sendBroadcast(
+                        configChangedIntent, Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
             }
         }
     };
@@ -4349,21 +4697,58 @@
      * Notify of a change to SRVCC state
      * @param state the new SRVCC state.
      */
-    public void notifySrvccState(Call.SrvccState state) {
+    public void notifySrvccState(int state) {
         if (DBG) log("notifySrvccState state=" + state);
 
-        mSrvccState = state;
+        if (mImsManager != null) {
+            try {
+                if (state == TelephonyManager.SRVCC_STATE_HANDOVER_STARTED) {
+                    mImsManager.notifySrvccStarted(mSrvccStartedCallback);
+                } else if (state == TelephonyManager.SRVCC_STATE_HANDOVER_COMPLETED) {
+                    mImsManager.notifySrvccCompleted();
+                } else if (state == TelephonyManager.SRVCC_STATE_HANDOVER_FAILED) {
+                    mImsManager.notifySrvccFailed();
+                } else if (state == TelephonyManager.SRVCC_STATE_HANDOVER_CANCELED) {
+                    mImsManager.notifySrvccCanceled();
+                }
+            } catch (ImsException e) {
+                loge("notifySrvccState : exception " + e);
+            }
+        }
 
-        if (mSrvccState == Call.SrvccState.COMPLETED) {
-            // If the dialing call had ringback, ensure it stops now, otherwise it'll keep playing
-            // afer the SRVCC completes.
-            mForegroundCall.maybeStopRingback();
+        switch(state) {
+            case TelephonyManager.SRVCC_STATE_HANDOVER_STARTED:
+                mSrvccState = Call.SrvccState.STARTED;
+                break;
 
-            resetState();
-            transferHandoverConnections(mForegroundCall);
-            transferHandoverConnections(mBackgroundCall);
-            transferHandoverConnections(mRingingCall);
-            updatePhoneState();
+            case TelephonyManager.SRVCC_STATE_HANDOVER_COMPLETED:
+                mSrvccState = Call.SrvccState.COMPLETED;
+
+                // If the dialing call had ringback, ensure it stops now,
+                // otherwise it'll keep playing afer the SRVCC completes.
+                mForegroundCall.maybeStopRingback();
+                mForegroundCall.maybeClearRemotelyHeldStatus();
+                mBackgroundCall.maybeClearRemotelyHeldStatus();
+
+                resetState();
+                transferHandoverConnections(mForegroundCall);
+                transferHandoverConnections(mBackgroundCall);
+                transferHandoverConnections(mRingingCall);
+                updatePhoneState();
+                mImsCallInfoTracker.notifySrvccCompleted();
+                break;
+
+            case TelephonyManager.SRVCC_STATE_HANDOVER_FAILED:
+                mSrvccState = Call.SrvccState.FAILED;
+                break;
+
+            case TelephonyManager.SRVCC_STATE_HANDOVER_CANCELED:
+                mSrvccState = Call.SrvccState.CANCELED;
+                break;
+
+            default:
+                //ignore invalid state
+                return;
         }
     }
 
@@ -4494,6 +4879,7 @@
                 try {
                     ImsFeature.Capabilities capabilities = (ImsFeature.Capabilities) args.arg1;
                     handleFeatureCapabilityChanged(capabilities);
+                    updateImsRegistrationInfo();
                 } finally {
                     args.recycle();
                 }
@@ -4576,6 +4962,55 @@
                 }
                 break;
             }
+
+            case EVENT_START_IMS_TRAFFIC_DONE: // fallthrough
+            case EVENT_CONNECTION_SETUP_FAILURE: {
+                ar = (AsyncResult) msg.obj;
+                // Not-null with EVENT_START_IMS_TRAFFIC_DONE
+                IImsTrafficSessionCallback callback = (IImsTrafficSessionCallback) ar.userObj;
+                try {
+                    if (ar.exception == null) {
+                        Object[] result = (Object[]) ar.result;
+                        if (result != null && result.length > 1) {
+                            if (callback == null) {
+                                //EVENT_CONNECTION_SETUP_FAILURE
+                                ImsTrafficSession session =
+                                        getImsTrafficSession((int) result[0]);
+                                if (session != null) callback = session.mCallback;
+                            }
+                            if (callback == null) break;
+
+                            if (result[1] == null) callback.onReady();
+                            else callback.onError((ConnectionFailureInfo) result[1]);
+                            break;
+                        }
+                    }
+                    if (callback != null) {
+                        callback.onError(new ConnectionFailureInfo(REASON_UNSPECIFIED, 0, -1));
+                    }
+                } catch (RemoteException e) {
+                    Rlog.e(LOG_TAG, "Exception: " + e);
+                }
+                break;
+            }
+
+            case EVENT_NEW_ACTIVE_CALL_STARTED: {
+                try {
+                    MediaQualityStatus status = mImsManager
+                            .queryMediaQualityStatus(MediaQualityStatus.MEDIA_SESSION_TYPE_AUDIO);
+                    if (status != null) {
+                        if (mPhone != null && mPhone.mDefaultPhone != null) {
+                            if (DBG) log("notify media quality status: " + status);
+                            mPhone.onMediaQualityStatusChanged(status);
+                        } else {
+                            loge("onMediaQualityStatusChanged: null phone");
+                        }
+                    }
+                } catch (ImsException e) {
+                    Rlog.e(LOG_TAG, "Exception in queryMediaQualityStatus: " + e);
+                }
+                break;
+            }
         }
     }
 
@@ -4750,6 +5185,7 @@
                         + mSupportSdpForRtpHeaderExtensions);
             }
         }
+        pw.println(" mSrvccTypeSupported=" + mSrvccTypeSupported);
         pw.println(" Event Log:");
         pw.increaseIndent();
         mOperationLocalLog.dump(pw);
@@ -5393,7 +5829,8 @@
                 mOnHoldToneStarted = true;
                 mOnHoldToneId = System.identityHashCode(conn);
             }
-            conn.onConnectionEvent(android.telecom.Connection.EVENT_CALL_REMOTELY_HELD, null);
+            conn.setRemotelyHeld();
+            mImsCallInfoTracker.updateImsCallStatus(conn, true, false);
 
             boolean useVideoPauseWorkaround = mPhone.getContext().getResources().getBoolean(
                     com.android.internal.R.bool.config_useVideoPauseWorkaround);
@@ -5521,4 +5958,256 @@
         }
         return false;
     }
+
+    private void initializeTerminalBasedCallWaiting() {
+        boolean capable = false;
+        if (mImsManager != null) {
+            try {
+                capable = mImsManager.isCapable(CAPABILITY_TERMINAL_BASED_CALL_WAITING);
+            } catch (ImsException e) {
+                loge("initializeTerminalBasedCallWaiting : exception " + e);
+            }
+        }
+        logi("initializeTerminalBasedCallWaiting capable=" + capable);
+        mPhone.setTerminalBasedCallWaitingSupported(capable);
+
+        if (capable) {
+            setTerminalBasedCallWaitingStatus(mPhone.getTerminalBasedCallWaitingState(false));
+        }
+    }
+
+    /**
+     * Notifies the change of the user setting of the terminal-based call waiting service
+     * to IMS service.
+     */
+    public void setTerminalBasedCallWaitingStatus(int state) {
+        if (state == TERMINAL_BASED_NOT_SUPPORTED) return;
+        if (mImsManager != null) {
+            try {
+                log("setTerminalBasedCallWaitingStatus state=" + state);
+                mImsManager.setTerminalBasedCallWaitingStatus(
+                        state == TERMINAL_BASED_ACTIVATED);
+            } catch (ImsException e) {
+                loge("setTerminalBasedCallWaitingStatus : exception " + e);
+            }
+        }
+    }
+
+    /** Send the list of SrvccConnection instances to the radio */
+    public void handleSrvccConnectionInfo(List<SrvccCall> profileList) {
+        mPhone.getDefaultPhone().mCi.setSrvccCallInfo(
+                convertToSrvccConnectionInfo(profileList), null);
+    }
+
+    /** Converts SrvccCall to SrvccConnection. */
+    @VisibleForTesting
+    public SrvccConnection[] convertToSrvccConnectionInfo(List<SrvccCall> profileList) {
+        if (mSrvccTypeSupported.isEmpty() || profileList == null || profileList.isEmpty()) {
+            loge("convertToSrvccConnectionInfo empty list");
+            return null;
+        }
+
+        List<SrvccConnection> connList = new ArrayList<>();
+        for (SrvccCall profile : profileList) {
+            if (isCallProfileSupported(profile)) {
+                addConnection(connList,
+                        profile, findConnection(profile.getCallId()));
+            } else {
+                logi("convertToSrvccConnectionInfo not supported"
+                        + " state=" + profile.getPreciseCallState());
+            }
+        }
+
+        logi("convertToSrvccConnectionInfo size=" + connList.size());
+        if (connList.isEmpty()) return null;
+        return connList.toArray(new SrvccConnection[0]);
+    }
+
+    /** Send the mediaType, direction, bitrate for ANBR Query to the radio */
+    public void handleSendAnbrQuery(int mediaType, int direction, int bitsPerSecond) {
+        if (DBG) log("handleSendAnbrQuery - mediaType=" + mediaType);
+        mPhone.getDefaultPhone().mCi.sendAnbrQuery(mediaType, direction, bitsPerSecond, null);
+    }
+
+
+    /**
+     * Notifies the recommended bit rate for the indicated logical channel and direction.
+     *
+     * @param mediaType MediaType is used to identify media stream such as audio or video.
+     * @param direction Direction of this packet stream (e.g. uplink or downlink).
+     * @param bitsPerSecond The recommended bit rate for the UE for a specific logical channel and
+     *        a specific direction by NW.
+     */
+    public void triggerNotifyAnbr(int mediaType, int direction, int bitsPerSecond) {
+        ImsCall activeCall = mForegroundCall.getFirstConnection().getImsCall();
+
+        if (activeCall != null) {
+            if (DBG) log("triggerNotifyAnbr - mediaType=" + mediaType);
+            activeCall.callSessionNotifyAnbr(mediaType, direction, bitsPerSecond);
+        }
+    }
+
+    private boolean isCallProfileSupported(SrvccCall profile) {
+        if (profile == null) {
+            loge("isCallProfileSupported null profile");
+            return false;
+        }
+
+        switch (profile.getPreciseCallState()) {
+            case PRECISE_CALL_STATE_ACTIVE:
+                return mSrvccTypeSupported.contains(BASIC_SRVCC_SUPPORT);
+            case PRECISE_CALL_STATE_HOLDING:
+                return mSrvccTypeSupported.contains(MIDCALL_SRVCC_SUPPORT);
+            case PRECISE_CALL_STATE_DIALING:
+                return mSrvccTypeSupported.contains(PREALERTING_SRVCC_SUPPORT);
+            case PRECISE_CALL_STATE_ALERTING:
+                return mSrvccTypeSupported.contains(ALERTING_SRVCC_SUPPORT);
+            case PRECISE_CALL_STATE_INCOMING:
+                return mSrvccTypeSupported.contains(ALERTING_SRVCC_SUPPORT);
+            case PRECISE_CALL_STATE_WAITING:
+                return mSrvccTypeSupported.contains(ALERTING_SRVCC_SUPPORT);
+            case PRECISE_CALL_STATE_INCOMING_SETUP:
+                return mSrvccTypeSupported.contains(PREALERTING_SRVCC_SUPPORT);
+            default:
+                loge("isCallProfileSupported invalid state="
+                        + profile.getPreciseCallState());
+                break;
+        }
+        return false;
+    }
+
+    private synchronized ImsPhoneConnection findConnection(String callId) {
+        for (ImsPhoneConnection c : mConnections) {
+            ImsCall imsCall = c.getImsCall();
+            if (imsCall == null) continue;
+            ImsCallSession session = imsCall.getCallSession();
+            if (session == null) continue;
+
+            if (TextUtils.equals(session.getCallId(), callId)) {
+                return c;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Update the list of SrvccConnection with the given SrvccCall and ImsPhoneconnection.
+     *
+     * @param destList the list of SrvccConnection the new connection will be added
+     * @param profile the SrvccCall of the connection to be added
+     * @param c the ImsPhoneConnection of the connection to be added
+     */
+    private void addConnection(
+            List<SrvccConnection> destList, SrvccCall profile, ImsPhoneConnection c) {
+        if (destList == null) return;
+        if (profile == null) return;
+
+        int preciseCallState = profile.getPreciseCallState();
+        if (!isAlive(preciseCallState)) {
+            Rlog.i(LOG_TAG, "addConnection not alive, " + preciseCallState);
+            return;
+        }
+
+        List<ConferenceParticipant> participants = getConferenceParticipants(c);
+        if (participants != null) {
+            for (ConferenceParticipant cp : participants) {
+                if (cp.getState() == android.telecom.Connection.STATE_DISCONNECTED) {
+                    Rlog.i(LOG_TAG, "addConnection participant is disconnected");
+                    continue;
+                }
+                SrvccConnection srvccConnection = new SrvccConnection(cp, preciseCallState);
+                Rlog.i(LOG_TAG, "addConnection " + srvccConnection);
+                destList.add(srvccConnection);
+            }
+        } else {
+            SrvccConnection srvccConnection =
+                    new SrvccConnection(profile.getImsCallProfile(), c, preciseCallState);
+            Rlog.i(LOG_TAG, "addConnection " + srvccConnection);
+            destList.add(srvccConnection);
+        }
+    }
+
+    private List<ConferenceParticipant> getConferenceParticipants(ImsPhoneConnection c) {
+        if (!mSrvccTypeSupported.contains(MIDCALL_SRVCC_SUPPORT)) return null;
+
+        ImsCall imsCall = c.getImsCall();
+        if (imsCall == null) return null;
+
+        List<ConferenceParticipant> participants = imsCall.getConferenceParticipants();
+        if (participants == null || participants.isEmpty()) return null;
+        return participants;
+    }
+
+    private static boolean isAlive(int preciseCallState) {
+        switch (preciseCallState) {
+            case PRECISE_CALL_STATE_ACTIVE: return true;
+            case PRECISE_CALL_STATE_HOLDING: return true;
+            case PRECISE_CALL_STATE_DIALING: return true;
+            case PRECISE_CALL_STATE_ALERTING: return true;
+            case PRECISE_CALL_STATE_INCOMING: return true;
+            case PRECISE_CALL_STATE_WAITING: return true;
+            case PRECISE_CALL_STATE_INCOMING_SETUP: return true;
+            default:
+        }
+        return false;
+    }
+
+    /**
+     * Notifies that radio triggered IMS deregistration.
+     * @param reason the reason why the deregistration is triggered.
+     */
+    public void triggerImsDeregistration(
+            @ImsRegistrationImplBase.ImsDeregistrationReason int reason) {
+        if (mImsManager == null) return;
+        try {
+            mImsManager.triggerDeregistration(reason);
+        } catch (ImsException e) {
+            loge("triggerImsDeregistration: exception " + e);
+        }
+    }
+
+    private void updateImsRegistrationInfo() {
+        int capabilities = 0;
+
+        if (mMmTelCapabilities.isCapable(
+                MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE)) {
+            capabilities |= IMS_MMTEL_CAPABILITY_VOICE;
+        }
+        if (mMmTelCapabilities.isCapable(
+                MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO)) {
+            capabilities |= IMS_MMTEL_CAPABILITY_VIDEO;
+        }
+        if (mMmTelCapabilities.isCapable(
+                MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_SMS)) {
+            capabilities |= IMS_MMTEL_CAPABILITY_SMS;
+        }
+
+        mPhone.updateImsRegistrationInfo(capabilities);
+    }
+
+    private void registerImsTrafficSession(int token,
+                @MmTelFeature.ImsTrafficType int trafficType,
+                @MmTelFeature.ImsTrafficDirection int trafficDirection,
+                @NonNull IImsTrafficSessionCallback callback) {
+        mImsTrafficSessions.put(Integer.valueOf(token),
+                new ImsTrafficSession(trafficType, trafficDirection, callback));
+    }
+
+    private void unregisterImsTrafficSession(int token) {
+        mImsTrafficSessions.remove(Integer.valueOf(token));
+    }
+
+    private ImsTrafficSession getImsTrafficSession(int token) {
+        return mImsTrafficSessions.get(Integer.valueOf(token));
+    }
+
+    private void stopAllImsTrafficTypes() {
+        boolean isEmpty = mImsTrafficSessions.isEmpty();
+        logi("stopAllImsTrafficTypes empty=" + isEmpty);
+
+        if (isEmpty) return;
+
+        mImsTrafficSessions.forEachKey(1, token -> mPhone.stopImsTraffic(token, null));
+        mImsTrafficSessions.clear();
+    }
 }
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
index 14952b7..7125763 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
@@ -511,6 +511,10 @@
     }
 
     @Override
+    public void getImei(Message response) {
+    }
+
+    @Override
     public void getCDMASubscription(Message response) {
     }
 
@@ -603,12 +607,13 @@
     public void iccOpenLogicalChannel(String AID, int p2, Message response) {}
 
     @Override
-    public void iccCloseLogicalChannel(int channel, Message response) {}
+    public void iccCloseLogicalChannel(int channel, boolean isEs10, Message response) {}
 
     @Override
     public void iccTransmitApduLogicalChannel(int channel, int cla, int instruction,
                                               int p1, int p2, int p3, String data,
-                                              Message response) {}
+                                              boolean isEs10Command, Message response) {}
+
     @Override
     public void iccTransmitApduBasicChannel(int cla, int instruction, int p1, int p2,
                                             int p3, String data, Message response) {}
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
old mode 100755
new mode 100644
index 68de4a3..b984d84
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
@@ -152,6 +152,11 @@
      */
     private ImsReasonInfo mImsReasonInfo;
 
+    /**
+     * Used to indicate that this call is held by remote party.
+     */
+    private boolean mIsHeldByRemote = false;
+
     //***** Event Constants
     private static final int EVENT_DTMF_DONE = 1;
     private static final int EVENT_PAUSE_DONE = 2;
@@ -244,7 +249,8 @@
 
     /** This is an MO call, created when dialing */
     public ImsPhoneConnection(Phone phone, String dialString, ImsPhoneCallTracker ct,
-            ImsPhoneCall parent, boolean isEmergency, boolean isWpsCall) {
+            ImsPhoneCall parent, boolean isEmergency, boolean isWpsCall,
+            ImsPhone.ImsDialArgs dialArgs) {
         super(PhoneConstants.PHONE_TYPE_IMS);
         createWakeLock(phone.getContext());
         acquireWakeLock();
@@ -272,6 +278,13 @@
         mIsEmergency = isEmergency;
         if (isEmergency) {
             setEmergencyCallInfo(mOwner);
+
+            if (getEmergencyNumberInfo() == null) {
+                // There was no emergency number info found for this call, however it is
+                // still marked as an emergency number. This may happen if it was a redialed
+                // non-detectable emergency call from IMS.
+                setNonDetectableEmergencyCallInfo(dialArgs.eccCategory);
+            }
         }
 
         mIsWpsCall = isWpsCall;
@@ -1580,7 +1593,30 @@
      */
     public void handleMergeComplete() {
         mIsMergeInProcess = false;
-        onConnectionEvent(android.telecom.Connection.EVENT_MERGE_COMPLETE, null);
+    }
+
+    /**
+     * Mark the call is held by remote party and inform to the UI.
+     */
+    public void setRemotelyHeld() {
+        mIsHeldByRemote = true;
+        onConnectionEvent(android.telecom.Connection.EVENT_CALL_REMOTELY_HELD, null);
+    }
+
+    /**
+     * Mark the call is Unheld by remote party and inform to the UI.
+     */
+    public void setRemotelyUnheld() {
+        mIsHeldByRemote = false;
+        onConnectionEvent(android.telecom.Connection.EVENT_CALL_REMOTELY_UNHELD, null);
+    }
+
+    /**
+     * @return whether the remote party is holding the call.
+     */
+    public boolean isHeldByRemote() {
+        Rlog.i(LOG_TAG, "isHeldByRemote=" + mIsHeldByRemote);
+        return mIsHeldByRemote;
     }
 
     public void changeToPausedState() {
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
index 738439a..25fa8a2 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
@@ -58,6 +58,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.CallForwardInfo;
 import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.CallWaitingController;
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.MmiCode;
@@ -1102,10 +1103,25 @@
                 int serviceClass = siToServiceClass(mSia);
 
                 if (isActivate() || isDeactivate()) {
+                    if (serviceClass == SERVICE_CLASS_NONE
+                            || (serviceClass & SERVICE_CLASS_VOICE) == SERVICE_CLASS_VOICE) {
+                        if (mPhone.getTerminalBasedCallWaitingState(false)
+                                != CallWaitingController.TERMINAL_BASED_NOT_SUPPORTED) {
+                            mPhone.getDefaultPhone().setCallWaiting(isActivate(), serviceClass,
+                                    obtainMessage(EVENT_SET_COMPLETE, this));
+                            return;
+                        }
+                    }
                     mPhone.setCallWaiting(isActivate(), serviceClass,
                             obtainMessage(EVENT_SET_COMPLETE, this));
                 } else if (isInterrogate()) {
-                    mPhone.getCallWaiting(obtainMessage(EVENT_QUERY_COMPLETE, this));
+                    if (mPhone.getTerminalBasedCallWaitingState(false)
+                            != CallWaitingController.TERMINAL_BASED_NOT_SUPPORTED) {
+                        mPhone.getDefaultPhone()
+                                .getCallWaiting(obtainMessage(EVENT_QUERY_COMPLETE, this));
+                    } else {
+                        mPhone.getCallWaiting(obtainMessage(EVENT_QUERY_COMPLETE, this));
+                    }
                 } else {
                     throw new RuntimeException ("Invalid or Unsupported MMI Code");
                 }
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsRegistrationCallbackHelper.java b/src/java/com/android/internal/telephony/imsphone/ImsRegistrationCallbackHelper.java
index 115f6fe..9452e2a 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsRegistrationCallbackHelper.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsRegistrationCallbackHelper.java
@@ -20,8 +20,10 @@
 import android.annotation.NonNull;
 import android.net.Uri;
 import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsRegistrationAttributes;
 import android.telephony.ims.RegistrationManager;
 import android.telephony.ims.aidl.IImsRegistrationCallback;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.util.Log;
 
 import java.util.concurrent.Executor;
@@ -40,7 +42,7 @@
         /**
          * Handle the callback when IMS is registered.
          */
-        void handleImsRegistered(int imsRadioTech);
+        void handleImsRegistered(@NonNull ImsRegistrationAttributes attributes);
 
         /**
          * Handle the callback when IMS is registering.
@@ -50,7 +52,9 @@
         /**
          * Handle the callback when IMS is unregistered.
          */
-        void handleImsUnregistered(ImsReasonInfo imsReasonInfo);
+        void handleImsUnregistered(ImsReasonInfo imsReasonInfo,
+                @RegistrationManager.SuggestedAction int suggestedAction,
+                @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech);
 
         /**
          * Handle the callback when the list of subscriber {@link Uri}s associated with this IMS
@@ -66,9 +70,9 @@
     private final RegistrationManager.RegistrationCallback mImsRegistrationCallback =
             new RegistrationManager.RegistrationCallback() {
                 @Override
-                public void onRegistered(int imsRadioTech) {
+                public void onRegistered(@NonNull ImsRegistrationAttributes attributes) {
                     updateRegistrationState(RegistrationManager.REGISTRATION_STATE_REGISTERED);
-                    mImsRegistrationUpdate.handleImsRegistered(imsRadioTech);
+                    mImsRegistrationUpdate.handleImsRegistered(attributes);
                 }
 
                 @Override
@@ -79,8 +83,17 @@
 
                 @Override
                 public void onUnregistered(ImsReasonInfo imsReasonInfo) {
+                    onUnregistered(imsReasonInfo, RegistrationManager.SUGGESTED_ACTION_NONE,
+                            ImsRegistrationImplBase.REGISTRATION_TECH_NONE);
+                }
+
+                @Override
+                public void onUnregistered(ImsReasonInfo imsReasonInfo,
+                        @RegistrationManager.SuggestedAction int suggestedAction,
+                        @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech) {
                     updateRegistrationState(RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED);
-                    mImsRegistrationUpdate.handleImsUnregistered(imsReasonInfo);
+                    mImsRegistrationUpdate.handleImsUnregistered(imsReasonInfo, suggestedAction,
+                            imsRadioTech);
                 }
 
                 @Override
diff --git a/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java b/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java
index 56db0c7..cfa16d0 100644
--- a/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java
+++ b/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java
@@ -16,10 +16,6 @@
 
 package com.android.internal.telephony.metrics;
 
-import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_HANDOVER;
-import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_NORMAL;
-import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_RADIO_OFF;
-import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_UNKNOWN;
 import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__IP_TYPE__APN_PROTOCOL_IPV4;
 
 import android.annotation.Nullable;
@@ -30,18 +26,18 @@
 import android.telephony.DataFailCause;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
 import android.telephony.data.ApnSetting.ProtocolType;
 import android.telephony.data.DataCallResponse;
 import android.telephony.data.DataService;
-import android.telephony.data.DataService.DeactivateDataReason;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.ServiceStateTracker;
-import com.android.internal.telephony.SubscriptionController;
+import com.android.internal.telephony.data.DataNetwork;
 import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
@@ -127,33 +123,17 @@
     /**
      * Updates the dataCall atom when data call is deactivated.
      *
-     * @param reason Deactivate reason
+     * @param reason Tear down reason
      */
-    public synchronized void setDeactivateDataCallReason(@DeactivateDataReason int reason) {
+    public synchronized void setDeactivateDataCallReason(@DataNetwork.TearDownReason int reason) {
         // there should've been another call to initiate the atom,
         // so this method is being called out of order -> no metric will be logged
         if (mDataCallSession == null) {
             loge("setDeactivateDataCallReason: no DataCallSession atom has been initiated.");
             return;
         }
-        switch (reason) {
-            case DataService.REQUEST_REASON_NORMAL:
-                mDataCallSession.deactivateReason =
-                        DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_NORMAL;
-                break;
-            case DataService.REQUEST_REASON_SHUTDOWN:
-                mDataCallSession.deactivateReason =
-                        DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_RADIO_OFF;
-                break;
-            case DataService.REQUEST_REASON_HANDOVER:
-                mDataCallSession.deactivateReason =
-                        DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_HANDOVER;
-                break;
-            default:
-                mDataCallSession.deactivateReason =
-                        DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_UNKNOWN;
-                break;
-        }
+        // Skip the pre-U enum. See enum DataDeactivateReasonEnum in enums.proto
+        mDataCallSession.deactivateReason = reason + DataService.REQUEST_REASON_HANDOVER + 1;
     }
 
     /**
@@ -262,9 +242,9 @@
         mDataCallSession.oosAtEnd = getIsOos();
         mDataCallSession.ongoing = false;
         // set if this data call is established for internet on the non-Dds
-        SubscriptionInfo subInfo = SubscriptionController.getInstance()
+        SubscriptionInfo subInfo = SubscriptionManagerService.getInstance()
                 .getSubscriptionInfo(mPhone.getSubId());
-        if (mPhone.getSubId() != SubscriptionController.getInstance().getDefaultDataSubId()
+        if (mPhone.getSubId() != SubscriptionManager.getDefaultDataSubscriptionId()
                 && ((mDataCallSession.apnTypeBitmask & ApnSetting.TYPE_DEFAULT)
                 == ApnSetting.TYPE_DEFAULT)
                 && subInfo != null && !subInfo.isOpportunistic()) {
@@ -326,7 +306,7 @@
         proto.setupFailed = false;
         proto.failureCause = DataFailCause.NONE;
         proto.suggestedRetryMillis = 0;
-        proto.deactivateReason = DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_UNKNOWN;
+        proto.deactivateReason = DataNetwork.TEAR_DOWN_REASON_NONE;
         proto.durationMinutes = 0;
         proto.ongoing = true;
         proto.handoverFailureCauses = new int[0];
@@ -343,13 +323,9 @@
     }
 
     private boolean getIsOpportunistic() {
-        if (mPhone.isSubscriptionManagerServiceEnabled()) {
-            SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
-                    .getSubscriptionInfoInternal(mPhone.getSubId());
-            return subInfo != null && subInfo.isOpportunistic();
-        }
-        SubscriptionController subController = SubscriptionController.getInstance();
-        return subController != null && subController.isOpportunistic(mPhone.getSubId());
+        SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
+                .getSubscriptionInfoInternal(mPhone.getSubId());
+        return subInfo != null && subInfo.isOpportunistic();
     }
 
     private boolean getIsOos() {
diff --git a/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java b/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java
index 9ad775f..2f22196 100644
--- a/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java
+++ b/src/java/com/android/internal/telephony/metrics/DataStallRecoveryStats.java
@@ -27,7 +27,6 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.ServiceStateTracker;
-import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.TelephonyStatsLog;
 import com.android.internal.telephony.data.DataStallRecoveryManager;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
@@ -147,12 +146,8 @@
     }
 
     private static boolean getIsOpportunistic(Phone phone) {
-        if (phone.isSubscriptionManagerServiceEnabled()) {
-            SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
-                    .getSubscriptionInfoInternal(phone.getSubId());
-            return subInfo != null && subInfo.isOpportunistic();
-        }
-        SubscriptionController subController = SubscriptionController.getInstance();
-        return subController != null && subController.isOpportunistic(phone.getSubId());
+        SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
+                .getSubscriptionInfoInternal(phone.getSubId());
+        return subInfo != null && subInfo.isOpportunistic();
     }
 }
diff --git a/src/java/com/android/internal/telephony/metrics/DeviceStateHelper.java b/src/java/com/android/internal/telephony/metrics/DeviceStateHelper.java
new file mode 100644
index 0000000..29729c8
--- /dev/null
+++ b/src/java/com/android/internal/telephony/metrics/DeviceStateHelper.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.metrics;
+
+import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_CLOSED;
+import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_FLIPPED;
+import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_HALF_OPENED;
+import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_OPENED;
+import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_UNKNOWN;
+
+import android.content.Context;
+import android.hardware.devicestate.DeviceStateManager;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
+
+import com.android.internal.telephony.Phone;
+
+/** Device state information like the fold state. */
+public class DeviceStateHelper {
+    private int mFoldState = CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_UNKNOWN;
+
+    public DeviceStateHelper(Context context) {
+        HandlerThread mHandlerThread = new HandlerThread("DeviceStateHelperThread");
+        mHandlerThread.start();
+        context.getSystemService(DeviceStateManager.class)
+                .registerCallback(
+                        new HandlerExecutor(new Handler(mHandlerThread.getLooper())),
+                        state -> {
+                            updateFoldState(state);
+                        });
+    }
+
+    private void updateFoldState(int posture) {
+        switch (posture) {
+            case 0:
+                mFoldState = CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_CLOSED;
+                break;
+            case 1:
+                mFoldState = CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_HALF_OPENED;
+                break;
+            case 2:
+                mFoldState = CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_OPENED;
+                break;
+            case 4:
+                mFoldState = CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_FLIPPED;
+                break;
+            default:
+                mFoldState = CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_UNKNOWN;
+        }
+        updateServiceStateStats();
+    }
+
+    private void updateServiceStateStats() {
+        for (Phone phone : MetricsCollector.getPhonesIfAny()) {
+            phone.getServiceStateTracker().getServiceStateStats().onFoldStateChanged(mFoldState);
+        }
+    }
+
+    public int getFoldState() {
+        return mFoldState;
+    }
+}
diff --git a/src/java/com/android/internal/telephony/metrics/DeviceTelephonyPropertiesStats.java b/src/java/com/android/internal/telephony/metrics/DeviceTelephonyPropertiesStats.java
new file mode 100644
index 0000000..51fe20c
--- /dev/null
+++ b/src/java/com/android/internal/telephony/metrics/DeviceTelephonyPropertiesStats.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 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 com.android.internal.telephony.PhoneFactory;
+
+/** Metrics for the telephony related properties on the device. */
+public class DeviceTelephonyPropertiesStats {
+    private static final String TAG = DeviceTelephonyPropertiesStats.class.getSimpleName();
+
+    /**
+     * Record whenever the auto data switch feature is toggled.
+     */
+    public static void recordAutoDataSwitchFeatureToggle() {
+        PersistAtomsStorage storage = PhoneFactory.getMetricsCollector().getAtomsStorage();
+        storage.recordToggledAutoDataSwitch();
+    }
+}
diff --git a/src/java/com/android/internal/telephony/metrics/EmergencyNumberStats.java b/src/java/com/android/internal/telephony/metrics/EmergencyNumberStats.java
new file mode 100644
index 0000000..2867b46
--- /dev/null
+++ b/src/java/com/android/internal/telephony/metrics/EmergencyNumberStats.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.metrics;
+
+import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__ROUTE__EMERGENCY_CALL_ROUTE_EMERGENCY;
+import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__ROUTE__EMERGENCY_CALL_ROUTE_NORMAL;
+import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__ROUTE__EMERGENCY_CALL_ROUTE_UNKNOWN;
+import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_AIEC;
+import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_AMBULANCE;
+import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE;
+import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_MARINE_GUARD;
+import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_MIEC;
+import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_MOUNTAIN_RESCUE;
+import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_POLICE;
+import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED;
+import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__SOURCES__EMERGENCY_NUMBER_SOURCE_DATABASE;
+import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__SOURCES__EMERGENCY_NUMBER_SOURCE_DEFAULT;
+import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__SOURCES__EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG;
+import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__SOURCES__EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING;
+import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO__SOURCES__EMERGENCY_NUMBER_SOURCE_SIM;
+
+import android.telephony.emergency.EmergencyNumber;
+import android.util.SparseIntArray;
+
+import com.android.internal.telephony.nano.PersistAtomsProto;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * EmergencyStats logs the atoms for consolidated emergency number list in framework. It also logs
+ * the details of a dialed emergency number. To avoid repeated information this class stores the
+ * emergency numbers list in map and verifies the information for duplicacy before logging it. Note:
+ * This locally stored information will erase on process restart scenarios (like reboot, crash,
+ * etc.).
+ */
+public class EmergencyNumberStats {
+
+    private static final String TAG = EmergencyNumberStats.class.getSimpleName();
+    private static final SparseIntArray sRoutesMap;
+    private static final SparseIntArray sServiceCategoriesMap;
+    private static final SparseIntArray sSourcesMap;
+    private static EmergencyNumberStats sInstance;
+
+    static {
+        sRoutesMap = new SparseIntArray() {
+            {
+                put(EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY,
+                        EMERGENCY_NUMBERS_INFO__ROUTE__EMERGENCY_CALL_ROUTE_EMERGENCY);
+                put(EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL,
+                        EMERGENCY_NUMBERS_INFO__ROUTE__EMERGENCY_CALL_ROUTE_NORMAL);
+                put(EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN,
+                        EMERGENCY_NUMBERS_INFO__ROUTE__EMERGENCY_CALL_ROUTE_UNKNOWN);
+            }
+        };
+
+        sServiceCategoriesMap = new SparseIntArray() {
+            {
+                put(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+                        EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED);
+                put(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE,
+                        EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_POLICE);
+                put(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AMBULANCE,
+                        EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_AMBULANCE);
+                put(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE,
+                        EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE);
+                put(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MARINE_GUARD,
+                        EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_MARINE_GUARD);
+                put(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MOUNTAIN_RESCUE,
+                        EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_MOUNTAIN_RESCUE);
+                put(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MIEC,
+                        EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_MIEC);
+                put(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AIEC,
+                        EMERGENCY_NUMBERS_INFO__SERVICE_CATEGORIES__EMERGENCY_SERVICE_CATEGORY_AIEC);
+            }
+        };
+
+        sSourcesMap = new SparseIntArray() {
+            {
+                put(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
+                        EMERGENCY_NUMBERS_INFO__SOURCES__EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING);
+                put(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_SIM,
+                        EMERGENCY_NUMBERS_INFO__SOURCES__EMERGENCY_NUMBER_SOURCE_SIM);
+                put(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+                        EMERGENCY_NUMBERS_INFO__SOURCES__EMERGENCY_NUMBER_SOURCE_DATABASE);
+                put(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG,
+                        EMERGENCY_NUMBERS_INFO__SOURCES__EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG);
+                put(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DEFAULT,
+                        EMERGENCY_NUMBERS_INFO__SOURCES__EMERGENCY_NUMBER_SOURCE_DEFAULT);
+            }
+        };
+    }
+
+    private EmergencyNumberStats() {
+    }
+
+    /** Static method to provide singleton instance for EmergencyNumberStats. */
+    public static EmergencyNumberStats getInstance() {
+        if (sInstance == null) {
+            sInstance = new EmergencyNumberStats();
+        }
+        return sInstance;
+    }
+
+    /**
+     * It converts the {@link android.telephony.emergency.EmergencyNumber} to
+     * {@link PersistAtomsProto.EmergencyNumber} for
+     * logging the EmergencyNumber atoms with pulled event.
+     *
+     * @param emergencyNumberList android.telephony.EmergencyNumber list
+     * @param assetVersion        assert version
+     * @param otaVersion          ota version
+     * @param isDbRoutingIgnored  flag that defines if routing is ignored through database.
+     */
+    public PersistAtomsProto.EmergencyNumbersInfo[] convertEmergencyNumbersListToProto(
+            List<EmergencyNumber> emergencyNumberList, int assetVersion, int otaVersion,
+            boolean isDbRoutingIgnored) {
+        List<PersistAtomsProto.EmergencyNumbersInfo> numberProtoList = new ArrayList<>();
+        for (EmergencyNumber number : emergencyNumberList) {
+            numberProtoList.add(convertEmergencyNumberToProto(number, assetVersion, otaVersion,
+                    isDbRoutingIgnored));
+        }
+        return numberProtoList.toArray(new PersistAtomsProto.EmergencyNumbersInfo[0]);
+    }
+
+    private PersistAtomsProto.EmergencyNumbersInfo convertEmergencyNumberToProto(
+            EmergencyNumber number, int assetVer, int otaVer, boolean isDbRoutingIgnored) {
+        String dialNumber = number.getNumber();
+        PersistAtomsProto.EmergencyNumbersInfo emergencyNumber =
+                new PersistAtomsProto.EmergencyNumbersInfo();
+        emergencyNumber.isDbVersionIgnored = isDbRoutingIgnored;
+        emergencyNumber.assetVersion = assetVer;
+        emergencyNumber.otaVersion = otaVer;
+        emergencyNumber.number = dialNumber;
+        emergencyNumber.countryIso = number.getCountryIso();
+        emergencyNumber.mnc = number.getMnc();
+        emergencyNumber.route = sRoutesMap.get(number.getEmergencyCallRouting());
+        emergencyNumber.urns = number.getEmergencyUrns().toArray(new String[0]);
+        emergencyNumber.serviceCategories = getMappedServiceCategories(
+                number.getEmergencyServiceCategories());
+        emergencyNumber.sources = getMappedSources(number.getEmergencyNumberSources());
+        return emergencyNumber;
+    }
+
+    private int[] getMappedServiceCategories(List<Integer> serviceCategories) {
+        if (serviceCategories == null || serviceCategories.isEmpty()) {
+            return null;
+        }
+        return serviceCategories.stream().map(sServiceCategoriesMap::get).mapToInt(
+                Integer::intValue).toArray();
+    }
+
+    private int[] getMappedSources(List<Integer> sources) {
+        if (sources == null || sources.isEmpty()) {
+            return null;
+        }
+        return sources.stream().map(sSourcesMap::get).mapToInt(Integer::intValue).toArray();
+    }
+}
diff --git a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
index 412930f..5e00987 100644
--- a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
+++ b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
@@ -16,15 +16,12 @@
 
 package com.android.internal.telephony.metrics;
 
-import static android.text.format.DateUtils.HOUR_IN_MILLIS;
-import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
-import static android.text.format.DateUtils.SECOND_IN_MILLIS;
-
 import static com.android.internal.telephony.TelephonyStatsLog.CARRIER_ID_TABLE_VERSION;
 import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_DATA_SERVICE_SWITCH;
 import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_SERVICE_STATE;
 import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION;
 import static com.android.internal.telephony.TelephonyStatsLog.DEVICE_TELEPHONY_PROPERTIES;
+import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO;
 import static com.android.internal.telephony.TelephonyStatsLog.GBA_EVENT;
 import static com.android.internal.telephony.TelephonyStatsLog.IMS_DEDICATED_BEARER_EVENT;
 import static com.android.internal.telephony.TelephonyStatsLog.IMS_DEDICATED_BEARER_LISTENER_EVENT;
@@ -33,11 +30,18 @@
 import static com.android.internal.telephony.TelephonyStatsLog.IMS_REGISTRATION_STATS;
 import static com.android.internal.telephony.TelephonyStatsLog.IMS_REGISTRATION_TERMINATION;
 import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS;
+import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SHORT_CODE_SMS;
 import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SMS;
 import static com.android.internal.telephony.TelephonyStatsLog.PER_SIM_STATUS;
 import static com.android.internal.telephony.TelephonyStatsLog.PRESENCE_NOTIFY_EVENT;
 import static com.android.internal.telephony.TelephonyStatsLog.RCS_ACS_PROVISIONING_STATS;
 import static com.android.internal.telephony.TelephonyStatsLog.RCS_CLIENT_PROVISIONING_STATS;
+import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_CONTROLLER;
+import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_INCOMING_DATAGRAM;
+import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_OUTGOING_DATAGRAM;
+import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_PROVISION;
+import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_SESSION;
+import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_SOS_MESSAGE_RECOMMENDER;
 import static com.android.internal.telephony.TelephonyStatsLog.SIM_SLOT_STATE;
 import static com.android.internal.telephony.TelephonyStatsLog.SIP_DELEGATE_STATS;
 import static com.android.internal.telephony.TelephonyStatsLog.SIP_MESSAGE_RESPONSE;
@@ -52,16 +56,20 @@
 
 import android.app.StatsManager;
 import android.content.Context;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.util.StatsEvent;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.TelephonyStatsLog;
+import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState;
 import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession;
+import com.android.internal.telephony.nano.PersistAtomsProto.EmergencyNumbersInfo;
 import com.android.internal.telephony.nano.PersistAtomsProto.GbaEvent;
 import com.android.internal.telephony.nano.PersistAtomsProto.ImsDedicatedBearerEvent;
 import com.android.internal.telephony.nano.PersistAtomsProto.ImsDedicatedBearerListenerEvent;
@@ -71,10 +79,17 @@
 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationTermination;
 import com.android.internal.telephony.nano.PersistAtomsProto.IncomingSms;
 import com.android.internal.telephony.nano.PersistAtomsProto.NetworkRequestsV2;
+import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingShortCodeSms;
 import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingSms;
 import com.android.internal.telephony.nano.PersistAtomsProto.PresenceNotifyEvent;
 import com.android.internal.telephony.nano.PersistAtomsProto.RcsAcsProvisioningStats;
 import com.android.internal.telephony.nano.PersistAtomsProto.RcsClientProvisioningStats;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteController;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteIncomingDatagram;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteOutgoingDatagram;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteProvision;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteSession;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteSosMessageRecommender;
 import com.android.internal.telephony.nano.PersistAtomsProto.SipDelegateStats;
 import com.android.internal.telephony.nano.PersistAtomsProto.SipMessageResponse;
 import com.android.internal.telephony.nano.PersistAtomsProto.SipTransportFeatureTagStats;
@@ -85,6 +100,7 @@
 import com.android.internal.util.ConcurrentUtils;
 import com.android.telephony.Rlog;
 
+import java.time.Duration;
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.List;
@@ -104,6 +120,10 @@
     /** Disables various restrictions to ease debugging during development. */
     private static final boolean DBG = false; // STOPSHIP if true
 
+    private static final long MILLIS_PER_HOUR = Duration.ofHours(1).toMillis();
+    private static final long MILLIS_PER_MINUTE = Duration.ofMinutes(1).toMillis();
+    private static final long MILLIS_PER_SECOND = Duration.ofSeconds(1).toMillis();
+
     /**
      * Sets atom pull cool down to 23 hours to help enforcing privacy requirement.
      *
@@ -111,7 +131,7 @@
      * that occur once a day.
      */
     private static final long MIN_COOLDOWN_MILLIS =
-            DBG ? 10L * SECOND_IN_MILLIS : 23L * HOUR_IN_MILLIS;
+            DBG ? 10L * MILLIS_PER_SECOND : 23L * MILLIS_PER_HOUR;
 
     /**
      * Buckets with less than these many calls will be dropped.
@@ -122,25 +142,28 @@
 
     /** Bucket size in milliseconds to round call durations into. */
     private static final long DURATION_BUCKET_MILLIS =
-            DBG ? 2L * SECOND_IN_MILLIS : 5L * MINUTE_IN_MILLIS;
+            DBG ? 2L * MILLIS_PER_SECOND : 5L * MILLIS_PER_MINUTE;
 
     private final PersistAtomsStorage mStorage;
+    private final DeviceStateHelper mDeviceStateHelper;
     private final StatsManager mStatsManager;
     private final AirplaneModeStats mAirplaneModeStats;
     private final Set<DataCallSessionStats> mOngoingDataCallStats = ConcurrentHashMap.newKeySet();
     private static final Random sRandom = new Random();
 
     public MetricsCollector(Context context) {
-        this(context, new PersistAtomsStorage(context));
+        this(context, new PersistAtomsStorage(context), new DeviceStateHelper(context));
     }
 
     /** Allows dependency injection. Used during unit tests. */
     @VisibleForTesting
-    public MetricsCollector(Context context,
-                            PersistAtomsStorage storage) {
+    public MetricsCollector(
+            Context context, PersistAtomsStorage storage, DeviceStateHelper deviceStateHelper) {
         mStorage = storage;
+        mDeviceStateHelper = deviceStateHelper;
         mStatsManager = (StatsManager) context.getSystemService(Context.STATS_MANAGER);
         if (mStatsManager != null) {
+            // Most (but not all) of these are subject to cooldown specified by MIN_COOLDOWN_MILLIS.
             registerAtom(CELLULAR_DATA_SERVICE_SWITCH);
             registerAtom(CELLULAR_SERVICE_STATE);
             registerAtom(SIM_SLOT_STATE);
@@ -169,6 +192,14 @@
             registerAtom(PRESENCE_NOTIFY_EVENT);
             registerAtom(GBA_EVENT);
             registerAtom(PER_SIM_STATUS);
+            registerAtom(OUTGOING_SHORT_CODE_SMS);
+            registerAtom(SATELLITE_CONTROLLER);
+            registerAtom(SATELLITE_SESSION);
+            registerAtom(SATELLITE_INCOMING_DATAGRAM);
+            registerAtom(SATELLITE_OUTGOING_DATAGRAM);
+            registerAtom(SATELLITE_PROVISION);
+            registerAtom(SATELLITE_SOS_MESSAGE_RECOMMENDER);
+            registerAtom(EMERGENCY_NUMBERS_INFO);
             Rlog.d(TAG, "registered");
         } else {
             Rlog.e(TAG, "could not get StatsManager, atoms not registered");
@@ -243,6 +274,22 @@
                 return pullGbaEvent(data);
             case PER_SIM_STATUS:
                 return pullPerSimStatus(data);
+            case OUTGOING_SHORT_CODE_SMS:
+                return pullOutgoingShortCodeSms(data);
+            case SATELLITE_CONTROLLER:
+                return pullSatelliteController(data);
+            case SATELLITE_SESSION:
+                return pullSatelliteSession(data);
+            case SATELLITE_INCOMING_DATAGRAM:
+                return pullSatelliteIncomingDatagram(data);
+            case SATELLITE_OUTGOING_DATAGRAM:
+                return pullSatelliteOutgoingDatagram(data);
+            case SATELLITE_PROVISION:
+                return pullSatelliteProvision(data);
+            case SATELLITE_SOS_MESSAGE_RECOMMENDER:
+                return pullSatelliteSosMessageRecommender(data);
+            case EMERGENCY_NUMBERS_INFO:
+                return pullEmergencyNumbersInfo(data);
             default:
                 Rlog.e(TAG, String.format("unexpected atom ID %d", atomTag));
                 return StatsManager.PULL_SKIP;
@@ -254,6 +301,23 @@
         return mStorage;
     }
 
+    /** Returns the {@link DeviceStateHelper}. */
+    public DeviceStateHelper getDeviceStateHelper() {
+        return mDeviceStateHelper;
+    }
+
+    /** Updates duration segments and calls {@link PersistAtomsStorage#flushAtoms()}. */
+    public void flushAtomsStorage() {
+        concludeAll();
+        mStorage.flushAtoms();
+    }
+
+    /** Updates duration segments and calls {@link PersistAtomsStorage#clearAtoms()}. */
+    public void clearAtomsStorage() {
+        concludeAll();
+        mStorage.clearAtoms();
+    }
+
     /**
      * Registers a {@link DataCallSessionStats} which will be pinged for on-going data calls when
      * data call atoms are pulled.
@@ -267,6 +331,44 @@
         mOngoingDataCallStats.remove(call);
     }
 
+    private void concludeDataCallSessionStats() {
+        for (DataCallSessionStats stats : mOngoingDataCallStats) {
+            stats.conclude();
+        }
+    }
+
+    private void concludeImsStats() {
+        for (Phone phone : getPhonesIfAny()) {
+            ImsPhone imsPhone = (ImsPhone) phone.getImsPhone();
+            if (imsPhone != null) {
+                imsPhone.getImsStats().conclude();
+            }
+        }
+    }
+
+    private void concludeServiceStateStats() {
+        for (Phone phone : getPhonesIfAny()) {
+            phone.getServiceStateTracker().getServiceStateStats().conclude();
+        }
+    }
+
+    private void concludeRcsStats() {
+        RcsStats rcsStats = RcsStats.getInstance();
+        if (rcsStats != null) {
+            rcsStats.concludeSipTransportFeatureTagsStat();
+            rcsStats.onFlushIncompleteRcsAcsProvisioningStats();
+            rcsStats.onFlushIncompleteImsRegistrationServiceDescStats();
+            rcsStats.onFlushIncompleteImsRegistrationFeatureTagStats();
+        }
+    }
+
+    private void concludeAll() {
+        concludeDataCallSessionStats();
+        concludeImsStats();
+        concludeServiceStateStats();
+        concludeRcsStats();
+    }
+
     private static int pullSimSlotState(List<StatsEvent> data) {
         SimSlotState state;
         try {
@@ -374,10 +476,7 @@
 
     private int pullDataCallSession(List<StatsEvent> data) {
         // Include ongoing data call segments
-        for (DataCallSessionStats stats : mOngoingDataCallStats) {
-            stats.conclude();
-        }
-
+        concludeDataCallSessionStats();
         DataCallSession[] dataCallSessions = mStorage.getDataCallSessions(MIN_COOLDOWN_MILLIS);
         if (dataCallSessions != null) {
             Arrays.stream(dataCallSessions)
@@ -405,10 +504,7 @@
 
     private int pullCellularServiceState(List<StatsEvent> data) {
         // Include the latest durations
-        for (Phone phone : getPhonesIfAny()) {
-            phone.getServiceStateTracker().getServiceStateStats().conclude();
-        }
-
+        concludeServiceStateStats();
         CellularServiceState[] persistAtoms =
                 mStorage.getCellularServiceStates(MIN_COOLDOWN_MILLIS);
         if (persistAtoms != null) {
@@ -424,13 +520,7 @@
 
     private int pullImsRegistrationStats(List<StatsEvent> data) {
         // Include the latest durations
-        for (Phone phone : getPhonesIfAny()) {
-            ImsPhone imsPhone = (ImsPhone) phone.getImsPhone();
-            if (imsPhone != null) {
-                imsPhone.getImsStats().conclude();
-            }
-        }
-
+        concludeImsStats();
         ImsRegistrationStats[] persistAtoms = mStorage.getImsRegistrationStats(MIN_COOLDOWN_MILLIS);
         if (persistAtoms != null) {
             // list is already shuffled when instances were inserted
@@ -469,13 +559,22 @@
         }
     }
 
-    private static int pullDeviceTelephonyProperties(List<StatsEvent> data) {
+    private int pullDeviceTelephonyProperties(List<StatsEvent> data) {
         Phone[] phones = getPhonesIfAny();
         if (phones.length == 0) {
             return StatsManager.PULL_SKIP;
         }
+        boolean isAutoDataSwitchOn = Arrays.stream(phones)
+                .anyMatch(phone ->
+                        phone.getSubId() != SubscriptionManager.getDefaultDataSubscriptionId()
+                                && phone.getDataSettingsManager().isMobileDataPolicyEnabled(
+                        TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH));
+        boolean hasDedicatedManagedProfileSub = Arrays.stream(phones)
+                .anyMatch(Phone::isManagedProfile);
 
-        data.add(TelephonyStatsLog.buildStatsEvent(DEVICE_TELEPHONY_PROPERTIES, true));
+        data.add(TelephonyStatsLog.buildStatsEvent(DEVICE_TELEPHONY_PROPERTIES, true,
+                isAutoDataSwitchOn, mStorage.getAutoDataSwitchToggleCount(),
+                hasDedicatedManagedProfileSub));
         return StatsManager.PULL_SUCCESS;
     }
 
@@ -677,13 +776,122 @@
                     perSimStatus.pin1Enabled, // isPin1Enabled
                     perSimStatus.minimumVoltageClass, // simVoltageClass
                     perSimStatus.userModifiedApnTypes, // userModifiedApnTypeBitmask
-                    perSimStatus.unmeteredNetworks); // unmeteredNetworks
+                    perSimStatus.unmeteredNetworks, // unmeteredNetworks
+                    perSimStatus.vonrEnabled); // vonrEnabled
             data.add(statsEvent);
             result = StatsManager.PULL_SUCCESS;
         }
         return result;
     }
 
+    private int pullOutgoingShortCodeSms(List<StatsEvent> data) {
+        OutgoingShortCodeSms[] outgoingShortCodeSmsList = mStorage
+                .getOutgoingShortCodeSms(MIN_COOLDOWN_MILLIS);
+        if (outgoingShortCodeSmsList != null) {
+            // Outgoing short code SMS list is already shuffled when SMS were inserted
+            Arrays.stream(outgoingShortCodeSmsList).forEach(sms -> data.add(buildStatsEvent(sms)));
+            return StatsManager.PULL_SUCCESS;
+        } else {
+            Rlog.w(TAG, "OUTGOING_SHORT_CODE_SMS pull too frequent, skipping");
+            return StatsManager.PULL_SKIP;
+        }
+    }
+
+    private int pullSatelliteController(List<StatsEvent> data) {
+        SatelliteController[] controllerAtoms =
+                mStorage.getSatelliteControllerStats(MIN_COOLDOWN_MILLIS);
+        if (controllerAtoms != null) {
+            Arrays.stream(controllerAtoms)
+                    .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
+            return StatsManager.PULL_SUCCESS;
+        } else {
+            Rlog.w(TAG, "SATELLITE_CONTROLLER pull too frequent, skipping");
+            return StatsManager.PULL_SKIP;
+        }
+    }
+
+    private int pullSatelliteSession(List<StatsEvent> data) {
+        SatelliteSession[] sessionAtoms =
+                mStorage.getSatelliteSessionStats(MIN_COOLDOWN_MILLIS);
+        if (sessionAtoms != null) {
+            Arrays.stream(sessionAtoms)
+                    .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
+            return StatsManager.PULL_SUCCESS;
+        } else {
+            Rlog.w(TAG, "SATELLITE_SESSION pull too frequent, skipping");
+            return StatsManager.PULL_SKIP;
+        }
+    }
+
+    private int pullSatelliteIncomingDatagram(List<StatsEvent> data) {
+        SatelliteIncomingDatagram[] incomingDatagramAtoms =
+                mStorage.getSatelliteIncomingDatagramStats(MIN_COOLDOWN_MILLIS);
+        if (incomingDatagramAtoms != null) {
+            Arrays.stream(incomingDatagramAtoms)
+                    .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
+            return StatsManager.PULL_SUCCESS;
+        } else {
+            Rlog.w(TAG, "SATELLITE_INCOMING_DATAGRAM pull too frequent, skipping");
+            return StatsManager.PULL_SKIP;
+        }
+    }
+
+
+    private int pullSatelliteOutgoingDatagram(List<StatsEvent> data) {
+        SatelliteOutgoingDatagram[] outgoingDatagramAtoms =
+                mStorage.getSatelliteOutgoingDatagramStats(MIN_COOLDOWN_MILLIS);
+        if (outgoingDatagramAtoms != null) {
+            Arrays.stream(outgoingDatagramAtoms)
+                    .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
+            return StatsManager.PULL_SUCCESS;
+        } else {
+            Rlog.w(TAG, "SATELLITE_OUTGOING_DATAGRAM pull too frequent, skipping");
+            return StatsManager.PULL_SKIP;
+        }
+    }
+
+
+    private int pullSatelliteProvision(List<StatsEvent> data) {
+        SatelliteProvision[] provisionAtoms =
+                mStorage.getSatelliteProvisionStats(MIN_COOLDOWN_MILLIS);
+        if (provisionAtoms != null) {
+            Arrays.stream(provisionAtoms)
+                    .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
+            return StatsManager.PULL_SUCCESS;
+        } else {
+            Rlog.w(TAG, "SATELLITE_PROVISION pull too frequent, skipping");
+            return StatsManager.PULL_SKIP;
+        }
+    }
+
+    private int pullSatelliteSosMessageRecommender(List<StatsEvent> data) {
+        SatelliteSosMessageRecommender[] sosMessageRecommenderAtoms =
+                mStorage.getSatelliteSosMessageRecommenderStats(MIN_COOLDOWN_MILLIS);
+        if (sosMessageRecommenderAtoms != null) {
+            Arrays.stream(sosMessageRecommenderAtoms)
+                    .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
+            return StatsManager.PULL_SUCCESS;
+        } else {
+            Rlog.w(TAG, "SATELLITE_SOS_MESSAGE_RECOMMENDER pull too frequent, skipping");
+            return StatsManager.PULL_SKIP;
+        }
+    }
+
+    private int pullEmergencyNumbersInfo(List<StatsEvent> data) {
+        boolean isDataLogged = false;
+        for (Phone phone : getPhonesIfAny()) {
+            if (phone != null) {
+                EmergencyNumberTracker tracker = phone.getEmergencyNumberTracker();
+                if (tracker != null) {
+                    EmergencyNumbersInfo[] numList = tracker.getEmergencyNumbersProtoArray();
+                    Arrays.stream(numList).forEach(number -> data.add(buildStatsEvent(number)));
+                    isDataLogged = true;
+                }
+            }
+        }
+        return isDataLogged ? StatsManager.PULL_SUCCESS : StatsManager.PULL_SKIP;
+    }
+
     /** Registers a pulled atom ID {@code atomId}. */
     private void registerAtom(int atomId) {
         mStatsManager.setPullAtomCallback(atomId, /* metadata= */ null,
@@ -712,8 +920,10 @@
                 state.simSlotIndex,
                 state.isMultiSim,
                 state.carrierId,
-                (int) (round(state.totalTimeMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
-                state.isEmergencyOnly);
+                roundAndConvertMillisToSeconds(state.totalTimeMillis),
+                state.isEmergencyOnly,
+                state.isInternetPdnUp,
+                state.foldState);
     }
 
     private static StatsEvent buildStatsEvent(VoiceCallRatUsage usage) {
@@ -721,7 +931,7 @@
                 VOICE_CALL_RAT_USAGE,
                 usage.carrierId,
                 usage.rat,
-                round(usage.totalDurationMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS,
+                roundAndConvertMillisToSeconds(usage.totalDurationMillis),
                 usage.callCount);
     }
 
@@ -764,7 +974,8 @@
                 session.ratAtConnected,
                 session.isMultiparty,
                 session.callDuration,
-                session.lastKnownRat);
+                session.lastKnownRat,
+                session.foldState);
     }
 
     private static StatsEvent buildStatsEvent(IncomingSms sms) {
@@ -784,7 +995,8 @@
                 sms.isEsim,
                 sms.carrierId,
                 sms.messageId,
-                sms.count);
+                sms.count,
+                sms.isManagedProfile);
     }
 
     private static StatsEvent buildStatsEvent(OutgoingSms sms) {
@@ -804,7 +1016,10 @@
                 sms.messageId,
                 sms.retryId,
                 sms.intervalMillis,
-                sms.count);
+                sms.count,
+                sms.sendErrorCode,
+                sms.networkErrorCode,
+                sms.isManagedProfile);
     }
 
     private static StatsEvent buildStatsEvent(DataCallSession dataCallSession) {
@@ -826,7 +1041,8 @@
                 dataCallSession.failureCause,
                 dataCallSession.suggestedRetryMillis,
                 dataCallSession.deactivateReason,
-                round(dataCallSession.durationMinutes, DURATION_BUCKET_MILLIS / MINUTE_IN_MILLIS),
+                roundAndConvertMillisToMinutes(
+                        dataCallSession.durationMinutes * MILLIS_PER_MINUTE),
                 dataCallSession.ongoing,
                 dataCallSession.bandAtEnd,
                 dataCallSession.handoverFailureCauses,
@@ -840,19 +1056,15 @@
                 stats.carrierId,
                 stats.simSlotIndex,
                 stats.rat,
-                (int) (round(stats.registeredMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
-                (int) (round(stats.voiceCapableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
-                (int)
-                        (round(stats.voiceAvailableMillis, DURATION_BUCKET_MILLIS)
-                                / SECOND_IN_MILLIS),
-                (int) (round(stats.smsCapableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
-                (int) (round(stats.smsAvailableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
-                (int) (round(stats.videoCapableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
-                (int)
-                        (round(stats.videoAvailableMillis, DURATION_BUCKET_MILLIS)
-                                / SECOND_IN_MILLIS),
-                (int) (round(stats.utCapableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
-                (int) (round(stats.utAvailableMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS));
+                roundAndConvertMillisToSeconds(stats.registeredMillis),
+                roundAndConvertMillisToSeconds(stats.voiceCapableMillis),
+                roundAndConvertMillisToSeconds(stats.voiceAvailableMillis),
+                roundAndConvertMillisToSeconds(stats.smsCapableMillis),
+                roundAndConvertMillisToSeconds(stats.smsAvailableMillis),
+                roundAndConvertMillisToSeconds(stats.videoCapableMillis),
+                roundAndConvertMillisToSeconds(stats.videoAvailableMillis),
+                roundAndConvertMillisToSeconds(stats.utCapableMillis),
+                roundAndConvertMillisToSeconds(stats.utAvailableMillis));
     }
 
     private static StatsEvent buildStatsEvent(ImsRegistrationTermination termination) {
@@ -883,7 +1095,7 @@
                 stats.slotId,
                 stats.featureTagName,
                 stats.registrationTech,
-                (int) (round(stats.registeredMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS));
+                roundAndConvertMillisToSeconds(stats.registeredMillis));
     }
 
     private static StatsEvent buildStatsEvent(RcsClientProvisioningStats stats) {
@@ -904,7 +1116,7 @@
                 stats.responseType,
                 stats.isSingleRegistrationEnabled,
                 stats.count,
-                (int) (round(stats.stateTimerMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS));
+                roundAndConvertMillisToSeconds(stats.stateTimerMillis));
     }
 
     private static StatsEvent buildStatsEvent(SipDelegateStats stats) {
@@ -913,7 +1125,7 @@
                 stats.dimension,
                 stats.carrierId,
                 stats.slotId,
-                (int) (round(stats.uptimeMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS),
+                roundAndConvertMillisToSeconds(stats.uptimeMillis),
                 stats.destroyReason);
     }
 
@@ -925,7 +1137,7 @@
                 stats.featureTagName,
                 stats.sipTransportDeniedReason,
                 stats.sipTransportDeregisteredReason,
-                (int) (round(stats.associatedMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS));
+                roundAndConvertMillisToSeconds(stats.associatedMillis));
     }
 
     private static StatsEvent buildStatsEvent(SipMessageResponse stats) {
@@ -985,7 +1197,7 @@
                 stats.serviceIdName,
                 stats.serviceIdVersion,
                 stats.registrationTech,
-                (int) (round(stats.publishedMillis, DURATION_BUCKET_MILLIS) / SECOND_IN_MILLIS));
+                roundAndConvertMillisToSeconds(stats.publishedMillis));
     }
 
     private static StatsEvent buildStatsEvent(UceEventStats stats) {
@@ -1023,8 +1235,97 @@
                 stats.count);
     }
 
+    private static StatsEvent buildStatsEvent(OutgoingShortCodeSms shortCodeSms) {
+        return TelephonyStatsLog.buildStatsEvent(
+                OUTGOING_SHORT_CODE_SMS,
+                shortCodeSms.category,
+                shortCodeSms.xmlVersion,
+                shortCodeSms.shortCodeSmsCount);
+    }
+
+    private static StatsEvent buildStatsEvent(SatelliteController satelliteController) {
+        return TelephonyStatsLog.buildStatsEvent(
+                SATELLITE_CONTROLLER,
+                satelliteController.countOfSatelliteServiceEnablementsSuccess,
+                satelliteController.countOfSatelliteServiceEnablementsFail,
+                satelliteController.countOfOutgoingDatagramSuccess,
+                satelliteController.countOfOutgoingDatagramFail,
+                satelliteController.countOfIncomingDatagramSuccess,
+                satelliteController.countOfIncomingDatagramFail,
+                satelliteController.countOfDatagramTypeSosSmsSuccess,
+                satelliteController.countOfDatagramTypeSosSmsFail,
+                satelliteController.countOfDatagramTypeLocationSharingSuccess,
+                satelliteController.countOfDatagramTypeLocationSharingFail,
+                satelliteController.countOfProvisionSuccess,
+                satelliteController.countOfProvisionFail,
+                satelliteController.countOfDeprovisionSuccess,
+                satelliteController.countOfDeprovisionFail,
+                satelliteController.totalServiceUptimeSec,
+                satelliteController.totalBatteryConsumptionPercent,
+                satelliteController.totalBatteryChargedTimeSec);
+    }
+
+    private static StatsEvent buildStatsEvent(SatelliteSession satelliteSession) {
+        return TelephonyStatsLog.buildStatsEvent(
+                SATELLITE_SESSION,
+                satelliteSession.satelliteServiceInitializationResult,
+                satelliteSession.satelliteTechnology,
+                satelliteSession.count);
+    }
+
+    private static StatsEvent buildStatsEvent(SatelliteIncomingDatagram stats) {
+        return TelephonyStatsLog.buildStatsEvent(
+                SATELLITE_INCOMING_DATAGRAM,
+                stats.resultCode,
+                stats.datagramSizeBytes,
+                stats.datagramTransferTimeMillis);
+    }
+
+    private static StatsEvent buildStatsEvent(SatelliteOutgoingDatagram stats) {
+        return TelephonyStatsLog.buildStatsEvent(
+                SATELLITE_OUTGOING_DATAGRAM,
+                stats.datagramType,
+                stats.resultCode,
+                stats.datagramSizeBytes,
+                stats.datagramTransferTimeMillis);
+    }
+
+    private static StatsEvent buildStatsEvent(SatelliteProvision stats) {
+        return TelephonyStatsLog.buildStatsEvent(
+                SATELLITE_PROVISION,
+                stats.resultCode,
+                stats.provisioningTimeSec,
+                stats.isProvisionRequest,
+                stats.isCanceled);
+    }
+
+    private static StatsEvent buildStatsEvent(SatelliteSosMessageRecommender stats) {
+        return TelephonyStatsLog.buildStatsEvent(
+                SATELLITE_SOS_MESSAGE_RECOMMENDER,
+                stats.isDisplaySosMessageSent,
+                stats.countOfTimerStarted,
+                stats.isImsRegistered,
+                stats.cellularServiceState,
+                stats.count);
+    }
+
+    private static StatsEvent buildStatsEvent(EmergencyNumbersInfo emergencyNumber) {
+        return TelephonyStatsLog.buildStatsEvent(
+                EMERGENCY_NUMBERS_INFO,
+                emergencyNumber.isDbVersionIgnored,
+                emergencyNumber.assetVersion,
+                emergencyNumber.otaVersion,
+                emergencyNumber.number,
+                emergencyNumber.countryIso,
+                emergencyNumber.mnc,
+                emergencyNumber.route,
+                emergencyNumber.urns,
+                emergencyNumber.serviceCategories,
+                emergencyNumber.sources);
+    }
+
     /** Returns all phones in {@link PhoneFactory}, or an empty array if phones not made yet. */
-    private static Phone[] getPhonesIfAny() {
+    static Phone[] getPhonesIfAny() {
         try {
             return PhoneFactory.getPhones();
         } catch (IllegalStateException e) {
@@ -1033,8 +1334,21 @@
         }
     }
 
-    /** Returns the value rounded to the bucket. */
-    private static long round(long value, long bucket) {
-        return bucket == 0 ? value : ((value + bucket / 2) / bucket) * bucket;
+    /**
+     * Rounds the duration and converts it from milliseconds to seconds.
+     */
+    private static int roundAndConvertMillisToSeconds(long valueMillis) {
+        long roundedValueMillis = Math.round((double) valueMillis / DURATION_BUCKET_MILLIS)
+                * DURATION_BUCKET_MILLIS;
+        return (int) (roundedValueMillis / MILLIS_PER_SECOND);
+    }
+
+    /**
+     * Rounds the duration and converts it from milliseconds to minutes.
+     */
+    private static int roundAndConvertMillisToMinutes(long valueMillis) {
+        long roundedValueMillis = Math.round((double) valueMillis / DURATION_BUCKET_MILLIS)
+                * DURATION_BUCKET_MILLIS;
+        return (int) (roundedValueMillis / MILLIS_PER_MINUTE);
     }
 }
diff --git a/src/java/com/android/internal/telephony/metrics/PerSimStatus.java b/src/java/com/android/internal/telephony/metrics/PerSimStatus.java
index 0b55815..bc1edc3 100644
--- a/src/java/com/android/internal/telephony/metrics/PerSimStatus.java
+++ b/src/java/com/android/internal/telephony/metrics/PerSimStatus.java
@@ -32,7 +32,6 @@
 import android.database.Cursor;
 import android.net.Uri;
 import android.provider.Telephony;
-import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
@@ -43,14 +42,11 @@
 import com.android.internal.telephony.IccCard;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
-import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.telephony.uicc.UiccSlot;
 
-import java.util.Optional;
-
 /** Stores the per SIM status. */
 public class PerSimStatus {
     private static final long BITMASK_2G =
@@ -76,6 +72,7 @@
     public final int minimumVoltageClass;
     public final int userModifiedApnTypes;
     public final long unmeteredNetworks;
+    public final boolean vonrEnabled;
 
     /** Returns the current sim status of the given {@link Phone}. */
     @Nullable
@@ -107,7 +104,8 @@
                 iccCard == null ? false : iccCard.getIccLockEnabled(),
                 getMinimumVoltageClass(phone),
                 getUserModifiedApnTypes(phone),
-                persistAtomsStorage.getUnmeteredNetworks(phone.getPhoneId(), carrierId));
+                persistAtomsStorage.getUnmeteredNetworks(phone.getPhoneId(), carrierId),
+                isVonrEnabled(phone));
     }
 
     private PerSimStatus(
@@ -126,7 +124,8 @@
             boolean pin1Enabled,
             int minimumVoltageClass,
             int userModifiedApnTypes,
-            long unmeteredNetworks) {
+            long unmeteredNetworks,
+            boolean vonrEnabled) {
         this.carrierId = carrierId;
         this.phoneNumberSourceUicc = phoneNumberSourceUicc;
         this.phoneNumberSourceCarrier = phoneNumberSourceCarrier;
@@ -143,6 +142,7 @@
         this.minimumVoltageClass = minimumVoltageClass;
         this.userModifiedApnTypes = userModifiedApnTypes;
         this.unmeteredNetworks = unmeteredNetworks;
+        this.vonrEnabled = vonrEnabled;
     }
 
     @Nullable
@@ -175,40 +175,21 @@
         String countryIso = "";
         String[] numbersFromAllSources;
 
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            if (SubscriptionManagerService.getInstance() == null) return null;
-            SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
-                    .getSubscriptionInfoInternal(phone.getSubId());
-            if (subInfo != null) {
-                countryIso = subInfo.getCountryIso();
-            }
-            numbersFromAllSources = new String[]{
-                    SubscriptionManagerService.getInstance().getPhoneNumber(phone.getSubId(),
-                            SubscriptionManager.PHONE_NUMBER_SOURCE_UICC, null, null),
-                    SubscriptionManagerService.getInstance().getPhoneNumber(phone.getSubId(),
-                            SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER, null, null),
-                    SubscriptionManagerService.getInstance().getPhoneNumber(phone.getSubId(),
-                            SubscriptionManager.PHONE_NUMBER_SOURCE_IMS, null, null)
-            };
-        } else {
-            SubscriptionController subscriptionController = SubscriptionController.getInstance();
-            if (subscriptionController == null) {
-                return null;
-            }
-            int subId = phone.getSubId();
-            countryIso = Optional.ofNullable(subscriptionController.getSubscriptionInfo(subId))
-                    .map(SubscriptionInfo::getCountryIso)
-                    .orElse("");
-            // numbersFromAllSources[] - phone numbers from each sources:
-            numbersFromAllSources = new String[]{
-                    subscriptionController.getPhoneNumber(subId,
-                            SubscriptionManager.PHONE_NUMBER_SOURCE_UICC, null, null), // 0
-                    subscriptionController.getPhoneNumber(subId,
-                            SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER, null, null), // 1
-                    subscriptionController.getPhoneNumber(subId,
-                            SubscriptionManager.PHONE_NUMBER_SOURCE_IMS, null, null), // 2
-            };
+        if (SubscriptionManagerService.getInstance() == null) return null;
+        SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
+                .getSubscriptionInfoInternal(phone.getSubId());
+        if (subInfo != null) {
+            countryIso = subInfo.getCountryIso();
         }
+        numbersFromAllSources = new String[]{
+                SubscriptionManagerService.getInstance().getPhoneNumber(phone.getSubId(),
+                        SubscriptionManager.PHONE_NUMBER_SOURCE_UICC, null, null),
+                SubscriptionManagerService.getInstance().getPhoneNumber(phone.getSubId(),
+                        SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER, null, null),
+                SubscriptionManagerService.getInstance().getPhoneNumber(phone.getSubId(),
+                        SubscriptionManager.PHONE_NUMBER_SOURCE_IMS, null, null)
+        };
+
         int[] numberIds = new int[numbersFromAllSources.length]; // default value 0
         for (int i = 0, idForNextUniqueNumber = 1; i < numberIds.length; i++) {
             if (TextUtils.isEmpty(numbersFromAllSources[i])) {
@@ -295,4 +276,16 @@
             return bitmask;
         }
     }
+
+    /** Returns true if VoNR is enabled */
+    private static boolean isVonrEnabled(Phone phone) {
+        TelephonyManager telephonyManager =
+                phone.getContext()
+                        .getSystemService(TelephonyManager.class);
+        if (telephonyManager == null) {
+            return false;
+        }
+        telephonyManager = telephonyManager.createForSubscriptionId(phone.getSubId());
+        return telephonyManager.isVoNrEnabled();
+    }
 }
diff --git a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
index eef88d2..5a21baf 100644
--- a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
+++ b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
@@ -42,11 +42,18 @@
 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationTermination;
 import com.android.internal.telephony.nano.PersistAtomsProto.IncomingSms;
 import com.android.internal.telephony.nano.PersistAtomsProto.NetworkRequestsV2;
+import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingShortCodeSms;
 import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingSms;
 import com.android.internal.telephony.nano.PersistAtomsProto.PersistAtoms;
 import com.android.internal.telephony.nano.PersistAtomsProto.PresenceNotifyEvent;
 import com.android.internal.telephony.nano.PersistAtomsProto.RcsAcsProvisioningStats;
 import com.android.internal.telephony.nano.PersistAtomsProto.RcsClientProvisioningStats;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteController;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteIncomingDatagram;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteOutgoingDatagram;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteProvision;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteSession;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteSosMessageRecommender;
 import com.android.internal.telephony.nano.PersistAtomsProto.SipDelegateStats;
 import com.android.internal.telephony.nano.PersistAtomsProto.SipMessageResponse;
 import com.android.internal.telephony.nano.PersistAtomsProto.SipTransportFeatureTagStats;
@@ -159,6 +166,13 @@
     /** Maximum number of GBA Event to store between pulls. */
     private final int mMaxNumGbaEventStats;
 
+    /** Maximum number of outgoing short code sms to store between pulls. */
+    private final int mMaxOutgoingShortCodeSms;
+
+    /** Maximum number of Satellite relevant stats to store between pulls. */
+    private final int mMaxNumSatelliteStats;
+    private final int mMaxNumSatelliteControllerStats = 1;
+
     /** Stores persist atoms and persist states of the puller. */
     @VisibleForTesting protected PersistAtoms mAtoms;
 
@@ -207,6 +221,8 @@
             mMaxNumUceEventStats = 5;
             mMaxNumPresenceNotifyEventStats = 10;
             mMaxNumGbaEventStats = 5;
+            mMaxOutgoingShortCodeSms = 5;
+            mMaxNumSatelliteStats = 5;
         } else {
             mMaxNumVoiceCallSessions = 50;
             mMaxNumSms = 25;
@@ -229,6 +245,8 @@
             mMaxNumUceEventStats = 25;
             mMaxNumPresenceNotifyEventStats = 50;
             mMaxNumGbaEventStats = 10;
+            mMaxOutgoingShortCodeSms = 10;
+            mMaxNumSatelliteStats = 15;
         }
 
         mAtoms = loadAtomsFromFile();
@@ -431,6 +449,14 @@
         }
     }
 
+    /**
+     * Store the number of times auto data switch feature is toggled.
+     */
+    public synchronized void recordToggledAutoDataSwitch() {
+        mAtoms.autoDataSwitchToggleCount++;
+        saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
+    }
+
     /** Adds a new {@link NetworkRequestsV2} to the storage. */
     public synchronized void addNetworkRequestsV2(NetworkRequestsV2 networkRequests) {
         NetworkRequestsV2 existingMetrics = find(networkRequests);
@@ -655,6 +681,113 @@
         }
     }
 
+    /** Adds an outgoing short code sms to the storage. */
+    public synchronized void addOutgoingShortCodeSms(OutgoingShortCodeSms shortCodeSms) {
+        OutgoingShortCodeSms existingOutgoingShortCodeSms = find(shortCodeSms);
+        if (existingOutgoingShortCodeSms != null) {
+            existingOutgoingShortCodeSms.shortCodeSmsCount += 1;
+        } else {
+            mAtoms.outgoingShortCodeSms = insertAtRandomPlace(mAtoms.outgoingShortCodeSms,
+                    shortCodeSms, mMaxOutgoingShortCodeSms);
+        }
+        saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
+    }
+
+    /** Adds a new {@link SatelliteController} to the storage. */
+    public synchronized void addSatelliteControllerStats(SatelliteController stats) {
+        // SatelliteController is a single data point
+        SatelliteController[] atomArray = mAtoms.satelliteController;
+        if (atomArray == null || atomArray.length == 0) {
+            atomArray = new SatelliteController[] {new SatelliteController()};
+        }
+
+        SatelliteController atom = atomArray[0];
+        atom.countOfSatelliteServiceEnablementsSuccess
+                += stats.countOfSatelliteServiceEnablementsSuccess;
+        atom.countOfSatelliteServiceEnablementsFail
+                += stats.countOfSatelliteServiceEnablementsFail;
+        atom.countOfOutgoingDatagramSuccess
+                += stats.countOfOutgoingDatagramSuccess;
+        atom.countOfOutgoingDatagramFail
+                += stats.countOfOutgoingDatagramFail;
+        atom.countOfIncomingDatagramSuccess
+                += stats.countOfIncomingDatagramSuccess;
+        atom.countOfIncomingDatagramFail
+                += stats.countOfIncomingDatagramFail;
+        atom.countOfDatagramTypeSosSmsSuccess
+                += stats.countOfDatagramTypeSosSmsSuccess;
+        atom.countOfDatagramTypeSosSmsFail
+                += stats.countOfDatagramTypeSosSmsFail;
+        atom.countOfDatagramTypeLocationSharingSuccess
+                += stats.countOfDatagramTypeLocationSharingSuccess;
+        atom.countOfDatagramTypeLocationSharingFail
+                += stats.countOfDatagramTypeLocationSharingFail;
+        atom.countOfProvisionSuccess
+                += stats.countOfProvisionSuccess;
+        atom.countOfProvisionFail
+                += stats.countOfProvisionFail;
+        atom.countOfDeprovisionSuccess
+                += stats.countOfDeprovisionSuccess;
+        atom.countOfDeprovisionFail
+                += stats.countOfDeprovisionFail;
+        atom.totalServiceUptimeSec
+                += stats.totalServiceUptimeSec;
+        atom.totalBatteryConsumptionPercent
+                += stats.totalBatteryConsumptionPercent;
+        atom.totalBatteryChargedTimeSec
+                += stats.totalBatteryChargedTimeSec;
+
+        mAtoms.satelliteController = atomArray;
+        saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
+    }
+
+    /** Adds a new {@link SatelliteSession} to the storage. */
+    public synchronized void addSatelliteSessionStats(SatelliteSession stats) {
+        SatelliteSession existingStats = find(stats);
+        if (existingStats != null) {
+            existingStats.count += 1;
+        } else {
+            mAtoms.satelliteSession =
+                    insertAtRandomPlace(mAtoms.satelliteSession, stats, mMaxNumSatelliteStats);
+        }
+        saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
+    }
+
+    /** Adds a new {@link SatelliteIncomingDatagram} to the storage. */
+    public synchronized void addSatelliteIncomingDatagramStats(SatelliteIncomingDatagram stats) {
+        mAtoms.satelliteIncomingDatagram =
+                insertAtRandomPlace(mAtoms.satelliteIncomingDatagram, stats, mMaxNumSatelliteStats);
+        saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
+    }
+
+    /** Adds a new {@link SatelliteOutgoingDatagram} to the storage. */
+    public synchronized void addSatelliteOutgoingDatagramStats(SatelliteOutgoingDatagram stats) {
+        mAtoms.satelliteOutgoingDatagram =
+                insertAtRandomPlace(mAtoms.satelliteOutgoingDatagram, stats, mMaxNumSatelliteStats);
+        saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
+    }
+
+    /** Adds a new {@link SatelliteProvision} to the storage. */
+    public synchronized void addSatelliteProvisionStats(SatelliteProvision stats) {
+        mAtoms.satelliteProvision =
+                insertAtRandomPlace(mAtoms.satelliteProvision, stats, mMaxNumSatelliteStats);
+        saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
+    }
+
+    /** Adds a new {@link SatelliteSosMessageRecommender} to the storage. */
+    public synchronized void addSatelliteSosMessageRecommenderStats(
+            SatelliteSosMessageRecommender stats) {
+        SatelliteSosMessageRecommender existingStats = find(stats);
+        if (existingStats != null) {
+            existingStats.count += 1;
+        } else {
+            mAtoms.satelliteSosMessageRecommender =
+                    insertAtRandomPlace(mAtoms.satelliteSosMessageRecommender, stats,
+                            mMaxNumSatelliteStats);
+        }
+        saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
+    }
+
     /**
      * Returns and clears the voice call sessions if last pulled longer than {@code
      * minIntervalMillis} ago, otherwise returns {@code null}.
@@ -865,6 +998,16 @@
         }
     }
 
+    /** @return the number of times auto data switch mobile data policy is toggled. */
+    public synchronized int getAutoDataSwitchToggleCount() {
+        int count = mAtoms.autoDataSwitchToggleCount;
+        if (count > 0) {
+            mAtoms.autoDataSwitchToggleCount = 0;
+            saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
+        }
+        return count;
+    }
+
     /**
      * Returns and clears the ImsRegistrationFeatureTagStats if last pulled longer than
      * {@code minIntervalMillis} ago, otherwise returns {@code null}.
@@ -1174,6 +1317,135 @@
         return bitmask;
     }
 
+    /**
+     * Returns and clears the OutgoingShortCodeSms if last pulled longer than {@code
+     * minIntervalMillis} ago, otherwise returns {@code null}.
+     */
+    @Nullable
+    public synchronized OutgoingShortCodeSms[] getOutgoingShortCodeSms(long minIntervalMillis) {
+        if ((getWallTimeMillis() - mAtoms.outgoingShortCodeSmsPullTimestampMillis)
+                > minIntervalMillis) {
+            mAtoms.outgoingShortCodeSmsPullTimestampMillis = getWallTimeMillis();
+            OutgoingShortCodeSms[] previousOutgoingShortCodeSms = mAtoms.outgoingShortCodeSms;
+            mAtoms.outgoingShortCodeSms = new OutgoingShortCodeSms[0];
+            saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
+            return previousOutgoingShortCodeSms;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns and clears the {@link SatelliteController} stats if last pulled longer than {@code
+     * minIntervalMillis} ago, otherwise returns {@code null}.
+     */
+    @Nullable
+    public synchronized SatelliteController[] getSatelliteControllerStats(long minIntervalMillis) {
+        if (getWallTimeMillis() - mAtoms.satelliteControllerPullTimestampMillis
+                > minIntervalMillis) {
+            mAtoms.satelliteControllerPullTimestampMillis = getWallTimeMillis();
+            SatelliteController[] statsArray = mAtoms.satelliteController;
+            mAtoms.satelliteController = new SatelliteController[0];
+            saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
+            return statsArray;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns and clears the {@link SatelliteSession} stats if last pulled longer than {@code
+     * minIntervalMillis} ago, otherwise returns {@code null}.
+     */
+    @Nullable
+    public synchronized SatelliteSession[] getSatelliteSessionStats(long minIntervalMillis) {
+        if (getWallTimeMillis() - mAtoms.satelliteSessionPullTimestampMillis
+                > minIntervalMillis) {
+            mAtoms.satelliteSessionPullTimestampMillis = getWallTimeMillis();
+            SatelliteSession[] statsArray = mAtoms.satelliteSession;
+            mAtoms.satelliteSession = new SatelliteSession[0];
+            saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
+            return statsArray;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns and clears the {@link SatelliteIncomingDatagram} stats if last pulled longer than
+     * {@code minIntervalMillis} ago, otherwise returns {@code null}.
+     */
+    @Nullable
+    public synchronized SatelliteIncomingDatagram[] getSatelliteIncomingDatagramStats(
+            long minIntervalMillis) {
+        if (getWallTimeMillis() - mAtoms.satelliteIncomingDatagramPullTimestampMillis
+                > minIntervalMillis) {
+            mAtoms.satelliteIncomingDatagramPullTimestampMillis = getWallTimeMillis();
+            SatelliteIncomingDatagram[] statsArray = mAtoms.satelliteIncomingDatagram;
+            mAtoms.satelliteIncomingDatagram = new SatelliteIncomingDatagram[0];
+            saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
+            return statsArray;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns and clears the {@link SatelliteOutgoingDatagram} stats if last pulled longer than
+     * {@code minIntervalMillis} ago, otherwise returns {@code null}.
+     */
+    @Nullable
+    public synchronized SatelliteOutgoingDatagram[] getSatelliteOutgoingDatagramStats(
+            long minIntervalMillis) {
+        if (getWallTimeMillis() - mAtoms.satelliteOutgoingDatagramPullTimestampMillis
+                > minIntervalMillis) {
+            mAtoms.satelliteOutgoingDatagramPullTimestampMillis = getWallTimeMillis();
+            SatelliteOutgoingDatagram[] statsArray = mAtoms.satelliteOutgoingDatagram;
+            mAtoms.satelliteOutgoingDatagram = new SatelliteOutgoingDatagram[0];
+            saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
+            return statsArray;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns and clears the {@link SatelliteProvision} stats if last pulled longer than {@code
+     * minIntervalMillis} ago, otherwise returns {@code null}.
+     */
+    @Nullable
+    public synchronized SatelliteProvision[] getSatelliteProvisionStats(long minIntervalMillis) {
+        if (getWallTimeMillis() - mAtoms.satelliteProvisionPullTimestampMillis
+                > minIntervalMillis) {
+            mAtoms.satelliteProvisionPullTimestampMillis = getWallTimeMillis();
+            SatelliteProvision[] statsArray = mAtoms.satelliteProvision;
+            mAtoms.satelliteProvision = new SatelliteProvision[0];
+            saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
+            return statsArray;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns and clears the {@link SatelliteSosMessageRecommender} stats if last pulled longer
+     * than {@code minIntervalMillis} ago, otherwise returns {@code null}.
+     */
+    @Nullable
+    public synchronized SatelliteSosMessageRecommender[] getSatelliteSosMessageRecommenderStats(
+            long minIntervalMillis) {
+        if (getWallTimeMillis() - mAtoms.satelliteSosMessageRecommenderPullTimestampMillis
+                > minIntervalMillis) {
+            mAtoms.satelliteProvisionPullTimestampMillis = getWallTimeMillis();
+            SatelliteSosMessageRecommender[] statsArray = mAtoms.satelliteSosMessageRecommender;
+            mAtoms.satelliteSosMessageRecommender = new SatelliteSosMessageRecommender[0];
+            saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
+            return statsArray;
+        } else {
+            return null;
+        }
+    }
+
     /** Saves {@link PersistAtoms} to a file in private storage immediately. */
     public synchronized void flushAtoms() {
         saveAtomsToFile(0);
@@ -1309,6 +1581,21 @@
                             atoms.unmeteredNetworks,
                             UnmeteredNetworks.class
                     );
+            atoms.outgoingShortCodeSms = sanitizeAtoms(atoms.outgoingShortCodeSms,
+                    OutgoingShortCodeSms.class, mMaxOutgoingShortCodeSms);
+            atoms.satelliteController = sanitizeAtoms(atoms.satelliteController,
+                            SatelliteController.class, mMaxNumSatelliteControllerStats);
+            atoms.satelliteSession = sanitizeAtoms(atoms.satelliteSession,
+                    SatelliteSession.class, mMaxNumSatelliteStats);
+            atoms.satelliteIncomingDatagram = sanitizeAtoms(atoms.satelliteIncomingDatagram,
+                            SatelliteIncomingDatagram.class, mMaxNumSatelliteStats);
+            atoms.satelliteOutgoingDatagram = sanitizeAtoms(atoms.satelliteOutgoingDatagram,
+                            SatelliteOutgoingDatagram.class, mMaxNumSatelliteStats);
+            atoms.satelliteProvision = sanitizeAtoms(atoms.satelliteProvision,
+                            SatelliteProvision.class, mMaxNumSatelliteStats);
+            atoms.satelliteSosMessageRecommender = sanitizeAtoms(
+                    atoms.satelliteSosMessageRecommender, SatelliteSosMessageRecommender.class,
+                    mMaxNumSatelliteStats);
 
             // out of caution, sanitize also the timestamps
             atoms.voiceCallRatUsagePullTimestampMillis =
@@ -1357,7 +1644,20 @@
                     sanitizeTimestamp(atoms.presenceNotifyEventPullTimestampMillis);
             atoms.gbaEventPullTimestampMillis =
                     sanitizeTimestamp(atoms.gbaEventPullTimestampMillis);
-
+            atoms.outgoingShortCodeSmsPullTimestampMillis =
+                    sanitizeTimestamp(atoms.outgoingShortCodeSmsPullTimestampMillis);
+            atoms.satelliteControllerPullTimestampMillis =
+                    sanitizeTimestamp(atoms.satelliteControllerPullTimestampMillis);
+            atoms.satelliteSessionPullTimestampMillis =
+                    sanitizeTimestamp(atoms.satelliteSessionPullTimestampMillis);
+            atoms.satelliteIncomingDatagramPullTimestampMillis =
+                    sanitizeTimestamp(atoms.satelliteIncomingDatagramPullTimestampMillis);
+            atoms.satelliteOutgoingDatagramPullTimestampMillis =
+                    sanitizeTimestamp(atoms.satelliteOutgoingDatagramPullTimestampMillis);
+            atoms.satelliteProvisionPullTimestampMillis =
+                    sanitizeTimestamp(atoms.satelliteProvisionPullTimestampMillis);
+            atoms.satelliteSosMessageRecommenderPullTimestampMillis =
+                    sanitizeTimestamp(atoms.satelliteSosMessageRecommenderPullTimestampMillis);
             return atoms;
         } catch (NoSuchFileException e) {
             Rlog.d(TAG, "PersistAtoms file not found");
@@ -1407,7 +1707,9 @@
                     && state.simSlotIndex == key.simSlotIndex
                     && state.isMultiSim == key.isMultiSim
                     && state.carrierId == key.carrierId
-                    && state.isEmergencyOnly == key.isEmergencyOnly) {
+                    && state.isEmergencyOnly == key.isEmergencyOnly
+                    && state.isInternetPdnUp == key.isInternetPdnUp
+                    && state.foldState == key.foldState) {
                 return state;
             }
         }
@@ -1725,6 +2027,53 @@
     }
 
     /**
+     * Returns OutgoingShortCodeSms atom that has same category, xmlVersion as the given one,
+     * or {@code null} if it does not exist.
+     */
+    private @Nullable OutgoingShortCodeSms find(OutgoingShortCodeSms key) {
+        for (OutgoingShortCodeSms shortCodeSms : mAtoms.outgoingShortCodeSms) {
+            if (shortCodeSms.category == key.category
+                    && shortCodeSms.xmlVersion == key.xmlVersion) {
+                return shortCodeSms;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns SatelliteOutgoingDatagram atom that has same values or {@code null}
+     * if it does not exist.
+     */
+    private @Nullable SatelliteSession find(
+            SatelliteSession key) {
+        for (SatelliteSession stats : mAtoms.satelliteSession) {
+            if (stats.satelliteServiceInitializationResult
+                    == key.satelliteServiceInitializationResult
+                    && stats.satelliteTechnology == key.satelliteTechnology) {
+                return stats;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns SatelliteOutgoingDatagram atom that has same values or {@code null}
+     * if it does not exist.
+     */
+    private @Nullable SatelliteSosMessageRecommender find(
+            SatelliteSosMessageRecommender key) {
+        for (SatelliteSosMessageRecommender stats : mAtoms.satelliteSosMessageRecommender) {
+            if (stats.isDisplaySosMessageSent == key.isDisplaySosMessageSent
+                    && stats.countOfTimerStarted == key.countOfTimerStarted
+                    && stats.isImsRegistered == key.isImsRegistered
+                    && stats.cellularServiceState == key.cellularServiceState) {
+                return stats;
+            }
+        }
+        return null;
+    }
+
+    /**
      * Inserts a new element in a random position in an array with a maximum size.
      *
      * <p>If the array is full, merge with existing item if possible or replace one item randomly.
@@ -1969,6 +2318,13 @@
         atoms.uceEventStatsPullTimestampMillis = currentTime;
         atoms.presenceNotifyEventPullTimestampMillis = currentTime;
         atoms.gbaEventPullTimestampMillis = currentTime;
+        atoms.outgoingShortCodeSmsPullTimestampMillis = currentTime;
+        atoms.satelliteControllerPullTimestampMillis = currentTime;
+        atoms.satelliteSessionPullTimestampMillis = currentTime;
+        atoms.satelliteIncomingDatagramPullTimestampMillis = currentTime;
+        atoms.satelliteOutgoingDatagramPullTimestampMillis = currentTime;
+        atoms.satelliteProvisionPullTimestampMillis = currentTime;
+        atoms.satelliteSosMessageRecommenderPullTimestampMillis = currentTime;
 
         Rlog.d(TAG, "created new PersistAtoms");
         return atoms;
diff --git a/src/java/com/android/internal/telephony/metrics/RcsStats.java b/src/java/com/android/internal/telephony/metrics/RcsStats.java
index a9191b6..8d24def 100644
--- a/src/java/com/android/internal/telephony/metrics/RcsStats.java
+++ b/src/java/com/android/internal/telephony/metrics/RcsStats.java
@@ -85,6 +85,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Random;
 import java.util.Set;
@@ -129,48 +130,51 @@
     private static final Map<String, Integer> FEATURE_TAGS = new HashMap<>();
 
     static {
-        FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_STANDALONE_MSG.trim().toLowerCase(),
+        FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_STANDALONE_MSG.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.IMS_FEATURE_TAG_STANDALONE_MSG);
-        FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_CHAT_IM.trim().toLowerCase(),
+        FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_CHAT_IM.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.IMS_FEATURE_TAG_CHAT_IM);
-        FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_CHAT_SESSION.trim().toLowerCase(),
+        FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_CHAT_SESSION.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.IMS_FEATURE_TAG_CHAT_SESSION);
-        FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_FILE_TRANSFER.trim().toLowerCase(),
+        FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_FILE_TRANSFER.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.IMS_FEATURE_TAG_FILE_TRANSFER);
-        FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_FILE_TRANSFER_VIA_SMS.trim().toLowerCase(),
+        FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_FILE_TRANSFER_VIA_SMS.trim()
+                        .toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.IMS_FEATURE_TAG_FILE_TRANSFER_VIA_SMS);
         FEATURE_TAGS.put(
-                FeatureTags.FEATURE_TAG_CALL_COMPOSER_ENRICHED_CALLING.trim().toLowerCase(),
+                FeatureTags.FEATURE_TAG_CALL_COMPOSER_ENRICHED_CALLING.trim()
+                        .toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.IMS_FEATURE_TAG_CALL_COMPOSER_ENRICHED_CALLING);
         FEATURE_TAGS.put(
-                FeatureTags.FEATURE_TAG_CALL_COMPOSER_VIA_TELEPHONY.trim().toLowerCase(),
+                FeatureTags.FEATURE_TAG_CALL_COMPOSER_VIA_TELEPHONY.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.IMS_FEATURE_TAG_CALL_COMPOSER_VIA_TELEPHONY);
-        FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_POST_CALL.trim().toLowerCase(),
+        FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_POST_CALL.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.IMS_FEATURE_TAG_POST_CALL);
-        FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_SHARED_MAP.trim().toLowerCase(),
+        FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_SHARED_MAP.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.IMS_FEATURE_TAG_SHARED_MAP);
-        FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_SHARED_SKETCH.trim().toLowerCase(),
+        FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_SHARED_SKETCH.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.IMS_FEATURE_TAG_SHARED_SKETCH);
-        FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_GEO_PUSH.trim().toLowerCase(),
+        FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_GEO_PUSH.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.IMS_FEATURE_TAG_GEO_PUSH);
-        FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_GEO_PUSH_VIA_SMS.trim().toLowerCase(),
+        FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_GEO_PUSH_VIA_SMS.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.IMS_FEATURE_TAG_GEO_PUSH_VIA_SMS);
         FEATURE_TAGS.put(
-                FeatureTags.FEATURE_TAG_CHATBOT_COMMUNICATION_USING_SESSION.trim().toLowerCase(),
+                FeatureTags.FEATURE_TAG_CHATBOT_COMMUNICATION_USING_SESSION.trim()
+                        .toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.IMS_FEATURE_TAG_CHATBOT_COMMUNICATION_USING_SESSION);
         String FeatureTag = FeatureTags.FEATURE_TAG_CHATBOT_COMMUNICATION_USING_STANDALONE_MSG;
-        FEATURE_TAGS.put(FeatureTag.trim().toLowerCase(),
+        FEATURE_TAGS.put(FeatureTag.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.IMS_FEATURE_TAG_CHATBOT_COMMUNICATION_USING_STANDALONE_MSG);
         FEATURE_TAGS.put(
-                FeatureTags.FEATURE_TAG_CHATBOT_VERSION_SUPPORTED.trim().toLowerCase(),
+                FeatureTags.FEATURE_TAG_CHATBOT_VERSION_SUPPORTED.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.IMS_FEATURE_TAG_CHATBOT_VERSION_SUPPORTED);
-        FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_CHATBOT_ROLE.trim().toLowerCase(),
+        FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_CHATBOT_ROLE.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.IMS_FEATURE_TAG_CHATBOT_ROLE);
-        FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_MMTEL.trim().toLowerCase(),
+        FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_MMTEL.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.IMS_FEATURE_TAG_MMTEL);
-        FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_VIDEO.trim().toLowerCase(),
+        FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_VIDEO.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.IMS_FEATURE_TAG_VIDEO);
-        FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_PRESENCE.trim().toLowerCase(),
+        FEATURE_TAGS.put(FeatureTags.FEATURE_TAG_PRESENCE.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.IMS_FEATURE_TAG_PRESENCE);
     }
 
@@ -183,34 +187,42 @@
     private static final Map<String, Integer> SERVICE_IDS = new HashMap<>();
 
     static {
-        SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_MMTEL.trim().toLowerCase(),
+        SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_MMTEL.trim().toLowerCase(Locale.ROOT),
                 IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_MMTEL);
-        SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_CHAT_V1.trim().toLowerCase(),
+        SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_CHAT_V1.trim().toLowerCase(Locale.ROOT),
                 IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_CHAT_V1);
-        SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_CHAT_V2.trim().toLowerCase(),
+        SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_CHAT_V2.trim().toLowerCase(Locale.ROOT),
                 IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_CHAT_V2);
-        SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_FT.trim().toLowerCase(),
+        SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_FT.trim().toLowerCase(Locale.ROOT),
                 IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_FT);
-        SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_FT_OVER_SMS.trim().toLowerCase(),
+        SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_FT_OVER_SMS.trim()
+                        .toLowerCase(Locale.ROOT),
                 IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_FT_OVER_SMS);
-        SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_GEO_PUSH.trim().toLowerCase(),
+        SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_GEO_PUSH.trim().toLowerCase(Locale.ROOT),
                 IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_GEO_PUSH);
-        SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_GEO_PUSH_VIA_SMS.trim().toLowerCase(),
+        SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_GEO_PUSH_VIA_SMS.trim()
+                        .toLowerCase(Locale.ROOT),
                 IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_GEO_PUSH_VIA_SMS);
-        SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_CALL_COMPOSER.trim().toLowerCase(),
+        SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_CALL_COMPOSER.trim()
+                        .toLowerCase(Locale.ROOT),
                 IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_CALL_COMPOSER);
-        SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_POST_CALL.trim().toLowerCase(),
+        SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_POST_CALL.trim()
+                        .toLowerCase(Locale.ROOT),
                 IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_POST_CALL);
-        SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_SHARED_MAP.trim().toLowerCase(),
+        SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_SHARED_MAP.trim()
+                        .toLowerCase(Locale.ROOT),
                 IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_SHARED_MAP);
-        SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_SHARED_SKETCH.trim().toLowerCase(),
+        SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_SHARED_SKETCH.trim()
+                        .toLowerCase(Locale.ROOT),
                 IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_SHARED_SKETCH);
-        SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_CHATBOT.trim().toLowerCase(),
+        SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_CHATBOT.trim().toLowerCase(Locale.ROOT),
                 IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_CHATBOT);
-        SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_CHATBOT_STANDALONE.trim().toLowerCase(),
+        SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_CHATBOT_STANDALONE.trim()
+                        .toLowerCase(Locale.ROOT),
                 IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_CHATBOT_STANDALONE
         );
-        SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_CHATBOT_ROLE.trim().toLowerCase(),
+        SERVICE_IDS.put(RcsContactPresenceTuple.SERVICE_ID_CHATBOT_ROLE.trim()
+                        .toLowerCase(Locale.ROOT),
                 IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_CHATBOT_ROLE);
     }
 
@@ -221,33 +233,33 @@
     private static final Map<String, Integer> MESSAGE_TYPE = new HashMap<>();
 
     static {
-        MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_INVITE.trim().toLowerCase(),
+        MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_INVITE.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.SIP_REQUEST_INVITE);
-        MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_ACK.trim().toLowerCase(),
+        MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_ACK.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.SIP_REQUEST_ACK);
-        MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_OPTIONS.trim().toLowerCase(),
+        MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_OPTIONS.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.SIP_REQUEST_OPTIONS);
-        MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_BYE.trim().toLowerCase(),
+        MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_BYE.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.SIP_REQUEST_BYE);
-        MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_CANCEL.trim().toLowerCase(),
+        MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_CANCEL.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.SIP_REQUEST_CANCEL);
-        MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_REGISTER.trim().toLowerCase(),
+        MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_REGISTER.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.SIP_REQUEST_REGISTER);
-        MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_PRACK.trim().toLowerCase(),
+        MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_PRACK.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.SIP_REQUEST_PRACK);
-        MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_SUBSCRIBE.trim().toLowerCase(),
+        MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_SUBSCRIBE.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.SIP_REQUEST_SUBSCRIBE);
-        MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_NOTIFY.trim().toLowerCase(),
+        MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_NOTIFY.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.SIP_REQUEST_NOTIFY);
-        MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_PUBLISH.trim().toLowerCase(),
+        MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_PUBLISH.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.SIP_REQUEST_PUBLISH);
-        MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_INFO.trim().toLowerCase(),
+        MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_INFO.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.SIP_REQUEST_INFO);
-        MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_REFER.trim().toLowerCase(),
+        MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_REFER.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.SIP_REQUEST_REFER);
-        MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_MESSAGE.trim().toLowerCase(),
+        MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_MESSAGE.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.SIP_REQUEST_MESSAGE);
-        MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_UPDATE.trim().toLowerCase(),
+        MESSAGE_TYPE.put(SIP_REQUEST_MESSAGE_TYPE_UPDATE.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.SIP_REQUEST_UPDATE);
     }
 
@@ -1570,28 +1582,28 @@
     /** Get a enum value from pre-defined feature tag name list */
     @VisibleForTesting
     public int convertTagNameToValue(@NonNull String tagName) {
-        return FEATURE_TAGS.getOrDefault(tagName.trim().toLowerCase(),
+        return FEATURE_TAGS.getOrDefault(tagName.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.IMS_FEATURE_TAG_CUSTOM);
     }
 
     /** Get a enum value from pre-defined service id list */
     @VisibleForTesting
     public int convertServiceIdToValue(@NonNull String serviceId) {
-        return SERVICE_IDS.getOrDefault(serviceId.trim().toLowerCase(),
+        return SERVICE_IDS.getOrDefault(serviceId.trim().toLowerCase(Locale.ROOT),
                 IMS_REGISTRATION_SERVICE_DESC_STATS__SERVICE_ID_NAME__SERVICE_ID_CUSTOM);
     }
 
     /** Get a enum value from pre-defined message type list */
     @VisibleForTesting
     public int convertMessageTypeToValue(@NonNull String messageType) {
-        return MESSAGE_TYPE.getOrDefault(messageType.trim().toLowerCase(),
+        return MESSAGE_TYPE.getOrDefault(messageType.trim().toLowerCase(Locale.ROOT),
                 TelephonyProtoEnums.SIP_REQUEST_CUSTOM);
     }
 
     /** Get a enum value from pre-defined reason list */
     @VisibleForTesting
     public int convertPresenceNotifyReason(@NonNull String reason) {
-        return NOTIFY_REASONS.getOrDefault(reason.trim().toLowerCase(),
+        return NOTIFY_REASONS.getOrDefault(reason.trim().toLowerCase(Locale.ROOT),
                 PRESENCE_NOTIFY_EVENT__REASON__REASON_CUSTOM);
     }
 
diff --git a/src/java/com/android/internal/telephony/metrics/SatelliteStats.java b/src/java/com/android/internal/telephony/metrics/SatelliteStats.java
new file mode 100644
index 0000000..7ff370c
--- /dev/null
+++ b/src/java/com/android/internal/telephony/metrics/SatelliteStats.java
@@ -0,0 +1,905 @@
+/*
+ * 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 com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteController;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteIncomingDatagram;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteOutgoingDatagram;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteProvision;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteSession;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteSosMessageRecommender;
+import com.android.telephony.Rlog;
+
+/** Tracks Satellite metrics for each phone */
+public class SatelliteStats {
+    private static final String TAG = SatelliteStats.class.getSimpleName();
+
+    private final PersistAtomsStorage mAtomsStorage =
+            PhoneFactory.getMetricsCollector().getAtomsStorage();
+
+    private static SatelliteStats sInstance = null;
+
+    /** Gets the instance of SatelliteStats */
+    public static SatelliteStats getInstance() {
+        if (sInstance == null) {
+            Rlog.d(TAG, "SatelliteStats created.");
+            synchronized (SatelliteStats.class) {
+                sInstance = new SatelliteStats();
+            }
+        }
+        return sInstance;
+    }
+
+    /**
+     * A data class to contain whole component of {@link SatelliteController) atom.
+     * Refer to {@link #onSatelliteControllerMetrics(SatelliteControllerParams)}.
+     */
+    public class SatelliteControllerParams {
+        private final int mCountOfSatelliteServiceEnablementsSuccess;
+        private final int mCountOfSatelliteServiceEnablementsFail;
+        private final int mCountOfOutgoingDatagramSuccess;
+        private final int mCountOfOutgoingDatagramFail;
+        private final int mCountOfIncomingDatagramSuccess;
+        private final int mCountOfIncomingDatagramFail;
+        private final int mCountOfDatagramTypeSosSmsSuccess;
+        private final int mCountOfDatagramTypeSosSmsFail;
+        private final int mCountOfDatagramTypeLocationSharingSuccess;
+        private final int mCountOfDatagramTypeLocationSharingFail;
+        private final int mCountOfProvisionSuccess;
+        private final int mCountOfProvisionFail;
+        private final int mCountOfDeprovisionSuccess;
+        private final int mCountOfDeprovisionFail;
+        private final int mTotalServiceUptimeSec;
+        private final int mTotalBatteryConsumptionPercent;
+        private final int mTotalBatteryChargedTimeSec;
+
+        private SatelliteControllerParams(Builder builder) {
+            this.mCountOfSatelliteServiceEnablementsSuccess =
+                    builder.mCountOfSatelliteServiceEnablementsSuccess;
+            this.mCountOfSatelliteServiceEnablementsFail =
+                    builder.mCountOfSatelliteServiceEnablementsFail;
+            this.mCountOfOutgoingDatagramSuccess = builder.mCountOfOutgoingDatagramSuccess;
+            this.mCountOfOutgoingDatagramFail = builder.mCountOfOutgoingDatagramFail;
+            this.mCountOfIncomingDatagramSuccess = builder.mCountOfIncomingDatagramSuccess;
+            this.mCountOfIncomingDatagramFail = builder.mCountOfIncomingDatagramFail;
+            this.mCountOfDatagramTypeSosSmsSuccess = builder.mCountOfDatagramTypeSosSmsSuccess;
+            this.mCountOfDatagramTypeSosSmsFail = builder.mCountOfDatagramTypeSosSmsFail;
+            this.mCountOfDatagramTypeLocationSharingSuccess =
+                    builder.mCountOfDatagramTypeLocationSharingSuccess;
+            this.mCountOfDatagramTypeLocationSharingFail =
+                    builder.mCountOfDatagramTypeLocationSharingFail;
+            this.mCountOfProvisionSuccess = builder.mCountOfProvisionSuccess;
+            this.mCountOfProvisionFail = builder.mCountOfProvisionFail;
+            this.mCountOfDeprovisionSuccess = builder.mCountOfDeprovisionSuccess;
+            this.mCountOfDeprovisionFail = builder.mCountOfDeprovisionFail;
+            this.mTotalServiceUptimeSec = builder.mTotalServiceUptimeSec;
+            this.mTotalBatteryConsumptionPercent = builder.mTotalBatteryConsumptionPercent;
+            this.mTotalBatteryChargedTimeSec = builder.mTotalBatteryChargedTimeSec;
+        }
+
+        public int getCountOfSatelliteServiceEnablementsSuccess() {
+            return mCountOfSatelliteServiceEnablementsSuccess;
+        }
+
+        public int getCountOfSatelliteServiceEnablementsFail() {
+            return mCountOfSatelliteServiceEnablementsFail;
+        }
+
+        public int getCountOfOutgoingDatagramSuccess() {
+            return mCountOfOutgoingDatagramSuccess;
+        }
+
+        public int getCountOfOutgoingDatagramFail() {
+            return mCountOfOutgoingDatagramFail;
+        }
+
+        public int getCountOfIncomingDatagramSuccess() {
+            return mCountOfIncomingDatagramSuccess;
+        }
+
+        public int getCountOfIncomingDatagramFail() {
+            return mCountOfIncomingDatagramFail;
+        }
+
+        public int getCountOfDatagramTypeSosSmsSuccess() {
+            return mCountOfDatagramTypeSosSmsSuccess;
+        }
+
+        public int getCountOfDatagramTypeSosSmsFail() {
+            return mCountOfDatagramTypeSosSmsFail;
+        }
+
+        public int getCountOfDatagramTypeLocationSharingSuccess() {
+            return mCountOfDatagramTypeLocationSharingSuccess;
+        }
+
+        public int getCountOfDatagramTypeLocationSharingFail() {
+            return mCountOfDatagramTypeLocationSharingFail;
+        }
+
+        public int getCountOfProvisionSuccess() {
+            return mCountOfProvisionSuccess;
+        }
+
+        public int getCountOfProvisionFail() {
+            return mCountOfProvisionFail;
+        }
+
+        public int getCountOfDeprovisionSuccess() {
+            return mCountOfDeprovisionSuccess;
+        }
+
+        public int getCountOfDeprovisionFail() {
+            return mCountOfDeprovisionFail;
+        }
+
+        public int getTotalServiceUptimeSec() {
+            return mTotalServiceUptimeSec;
+        }
+
+        public int getTotalBatteryConsumptionPercent() {
+            return mTotalBatteryConsumptionPercent;
+        }
+
+        public int getTotalBatteryChargedTimeSec() {
+            return mTotalBatteryChargedTimeSec;
+        }
+
+        /**
+         * A builder class to create {@link SatelliteControllerParams} data structure class
+         */
+        public static class Builder {
+            private int mCountOfSatelliteServiceEnablementsSuccess = 0;
+            private int mCountOfSatelliteServiceEnablementsFail = 0;
+            private int mCountOfOutgoingDatagramSuccess = 0;
+            private int mCountOfOutgoingDatagramFail = 0;
+            private int mCountOfIncomingDatagramSuccess = 0;
+            private int mCountOfIncomingDatagramFail = 0;
+            private int mCountOfDatagramTypeSosSmsSuccess = 0;
+            private int mCountOfDatagramTypeSosSmsFail = 0;
+            private int mCountOfDatagramTypeLocationSharingSuccess = 0;
+            private int mCountOfDatagramTypeLocationSharingFail = 0;
+            private int mCountOfProvisionSuccess;
+            private int mCountOfProvisionFail;
+            private int mCountOfDeprovisionSuccess;
+            private int mCountOfDeprovisionFail;
+            private int mTotalServiceUptimeSec = 0;
+            private int mTotalBatteryConsumptionPercent = 0;
+            private int mTotalBatteryChargedTimeSec = 0;
+
+            /**
+             * Sets countOfSatelliteServiceEnablementsSuccess value of {@link SatelliteController}
+             * atom then returns Builder class
+             */
+            public Builder setCountOfSatelliteServiceEnablementsSuccess(
+                    int countOfSatelliteServiceEnablementsSuccess) {
+                this.mCountOfSatelliteServiceEnablementsSuccess =
+                        countOfSatelliteServiceEnablementsSuccess;
+                return this;
+            }
+
+            /**
+             * Sets countOfSatelliteServiceEnablementsFail value of {@link SatelliteController} atom
+             * then returns Builder class
+             */
+            public Builder setCountOfSatelliteServiceEnablementsFail(
+                    int countOfSatelliteServiceEnablementsFail) {
+                this.mCountOfSatelliteServiceEnablementsFail =
+                        countOfSatelliteServiceEnablementsFail;
+                return this;
+            }
+
+            /**
+             * Sets countOfOutgoingDatagramSuccess value of {@link SatelliteController} atom then
+             * returns Builder class
+             */
+            public Builder setCountOfOutgoingDatagramSuccess(int countOfOutgoingDatagramSuccess) {
+                this.mCountOfOutgoingDatagramSuccess = countOfOutgoingDatagramSuccess;
+                return this;
+            }
+
+            /**
+             * Sets countOfOutgoingDatagramFail value of {@link SatelliteController} atom then
+             * returns Builder class
+             */
+            public Builder setCountOfOutgoingDatagramFail(int countOfOutgoingDatagramFail) {
+                this.mCountOfOutgoingDatagramFail = countOfOutgoingDatagramFail;
+                return this;
+            }
+
+            /**
+             * Sets countOfIncomingDatagramSuccess value of {@link SatelliteController} atom then
+             * returns Builder class
+             */
+            public Builder setCountOfIncomingDatagramSuccess(int countOfIncomingDatagramSuccess) {
+                this.mCountOfIncomingDatagramSuccess = countOfIncomingDatagramSuccess;
+                return this;
+            }
+
+            /**
+             * Sets countOfIncomingDatagramFail value of {@link SatelliteController} atom then
+             * returns Builder class
+             */
+            public Builder setCountOfIncomingDatagramFail(int countOfIncomingDatagramFail) {
+                this.mCountOfIncomingDatagramFail = countOfIncomingDatagramFail;
+                return this;
+            }
+
+            /**
+             * Sets countOfDatagramTypeSosSmsSuccess value of {@link SatelliteController} atom then
+             * returns Builder class
+             */
+            public Builder setCountOfDatagramTypeSosSmsSuccess(
+                    int countOfDatagramTypeSosSmsSuccess) {
+                this.mCountOfDatagramTypeSosSmsSuccess = countOfDatagramTypeSosSmsSuccess;
+                return this;
+            }
+
+            /**
+             * Sets countOfDatagramTypeSosSmsFail value of {@link SatelliteController} atom then
+             * returns Builder class
+             */
+            public Builder setCountOfDatagramTypeSosSmsFail(int countOfDatagramTypeSosSmsFail) {
+                this.mCountOfDatagramTypeSosSmsFail = countOfDatagramTypeSosSmsFail;
+                return this;
+            }
+
+            /**
+             * Sets countOfDatagramTypeLocationSharingSuccess value of {@link SatelliteController}
+             * atom then returns Builder class
+             */
+            public Builder setCountOfDatagramTypeLocationSharingSuccess(
+                    int countOfDatagramTypeLocationSharingSuccess) {
+                this.mCountOfDatagramTypeLocationSharingSuccess =
+                        countOfDatagramTypeLocationSharingSuccess;
+                return this;
+            }
+
+            /**
+             * Sets countOfDatagramTypeLocationSharingFail value of {@link SatelliteController}
+             * atom then returns Builder class
+             */
+            public Builder setCountOfDatagramTypeLocationSharingFail(
+                    int countOfDatagramTypeLocationSharingFail) {
+                this.mCountOfDatagramTypeLocationSharingFail =
+                        countOfDatagramTypeLocationSharingFail;
+                return this;
+            }
+
+            /**
+             * Sets countOfProvisionSuccess value of {@link SatelliteController}
+             * atom then returns Builder class
+             */
+            public Builder setCountOfProvisionSuccess(int countOfProvisionSuccess) {
+                this.mCountOfProvisionSuccess = countOfProvisionSuccess;
+                return this;
+            }
+
+            /**
+             * Sets countOfProvisionFail value of {@link SatelliteController}
+             * atom then returns Builder class
+             */
+            public Builder setCountOfProvisionFail(int countOfProvisionFail) {
+                this.mCountOfProvisionFail = countOfProvisionFail;
+                return this;
+            }
+
+            /**
+             * Sets countOfDeprovisionSuccess value of {@link SatelliteController}
+             * atom then returns Builder class
+             */
+            public Builder setCountOfDeprovisionSuccess(int countOfDeprovisionSuccess) {
+                this.mCountOfDeprovisionSuccess = countOfDeprovisionSuccess;
+                return this;
+            }
+
+            /**
+             * Sets countOfDeprovisionSuccess value of {@link SatelliteController}
+             * atom then returns Builder class
+             */
+            public Builder setCountOfDeprovisionFail(int countOfDeprovisionFail) {
+                this.mCountOfDeprovisionFail = countOfDeprovisionFail;
+                return this;
+            }
+
+            /**
+             * Sets totalServiceUptimeSec value of {@link SatelliteController} atom then
+             * returns Builder class
+             */
+            public Builder setTotalServiceUptimeSec(int totalServiceUptimeSec) {
+                this.mTotalServiceUptimeSec = totalServiceUptimeSec;
+                return this;
+            }
+
+            /**
+             * Sets totalBatteryConsumptionPercent value of {@link SatelliteController} atom then
+             * returns Builder class
+             */
+            public Builder setTotalBatteryConsumptionPercent(int totalBatteryConsumptionPercent) {
+                this.mTotalBatteryConsumptionPercent = totalBatteryConsumptionPercent;
+                return this;
+            }
+
+            /**
+             * Sets totalBatteryChargedTimeSec value of {@link SatelliteController} atom then
+             * returns Builder class
+             */
+            public Builder setTotalBatteryChargedTimeSec(int totalBatteryChargedTimeSec) {
+                this.mTotalBatteryChargedTimeSec = totalBatteryChargedTimeSec;
+                return this;
+            }
+
+            /**
+             * Returns ControllerParams, which contains whole component of
+             * {@link SatelliteController} atom
+             */
+            public SatelliteControllerParams build() {
+                return new SatelliteStats()
+                        .new SatelliteControllerParams(this);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "ControllerParams("
+                    + ", countOfSatelliteServiceEnablementsSuccess="
+                    + mCountOfSatelliteServiceEnablementsSuccess
+                    + ", countOfSatelliteServiceEnablementsFail="
+                    + mCountOfSatelliteServiceEnablementsFail
+                    + ", countOfOutgoingDatagramSuccess=" + mCountOfOutgoingDatagramSuccess
+                    + ", countOfOutgoingDatagramFail=" + mCountOfOutgoingDatagramFail
+                    + ", countOfIncomingDatagramSuccess=" + mCountOfIncomingDatagramSuccess
+                    + ", countOfIncomingDatagramFail=" + mCountOfIncomingDatagramFail
+                    + ", countOfDatagramTypeSosSms=" + mCountOfDatagramTypeSosSmsSuccess
+                    + ", countOfDatagramTypeSosSms=" + mCountOfDatagramTypeSosSmsFail
+                    + ", countOfDatagramTypeLocationSharing="
+                    + mCountOfDatagramTypeLocationSharingSuccess
+                    + ", countOfDatagramTypeLocationSharing="
+                    + mCountOfDatagramTypeLocationSharingFail
+                    + ", serviceUptimeSec=" + mTotalServiceUptimeSec
+                    + ", batteryConsumptionPercent=" + mTotalBatteryConsumptionPercent
+                    + ", batteryChargedTimeSec=" + mTotalBatteryChargedTimeSec
+                    + ")";
+        }
+    }
+
+    /**
+     * A data class to contain whole component of {@link SatelliteSession) atom.
+     * Refer to {@link #onSatelliteSessionMetrics(SatelliteSessionParams)}.
+     */
+    public class SatelliteSessionParams {
+        private final int mSatelliteServiceInitializationResult;
+        private final int mSatelliteTechnology;
+
+        private SatelliteSessionParams(Builder builder) {
+            this.mSatelliteServiceInitializationResult =
+                    builder.mSatelliteServiceInitializationResult;
+            this.mSatelliteTechnology = builder.mSatelliteTechnology;
+        }
+
+        public int getSatelliteServiceInitializationResult() {
+            return mSatelliteServiceInitializationResult;
+        }
+
+        public int getSatelliteTechnology() {
+            return mSatelliteTechnology;
+        }
+
+        /**
+         * A builder class to create {@link SatelliteSessionParams} data structure class
+         */
+        public static class Builder {
+            private int mSatelliteServiceInitializationResult = -1;
+            private int mSatelliteTechnology = -1;
+
+            /**
+             * Sets satelliteServiceInitializationResult value of {@link SatelliteSession}
+             * atom then returns Builder class
+             */
+            public Builder setSatelliteServiceInitializationResult(
+                    int satelliteServiceInitializationResult) {
+                this.mSatelliteServiceInitializationResult = satelliteServiceInitializationResult;
+                return this;
+            }
+
+            /**
+             * Sets satelliteTechnology value of {@link SatelliteSession} atoms then
+             * returns Builder class
+             */
+            public Builder setSatelliteTechnology(int satelliteTechnology) {
+                this.mSatelliteTechnology = satelliteTechnology;
+                return this;
+            }
+
+            /**
+             * Returns SessionParams, which contains whole component of
+             * {@link SatelliteSession} atom
+             */
+            public SatelliteSessionParams build() {
+                return new SatelliteStats()
+                        .new SatelliteSessionParams(this);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "SessionParams("
+                    + ", satelliteServiceInitializationResult="
+                    + mSatelliteServiceInitializationResult
+                    + ", satelliteTechnology=" + mSatelliteTechnology
+                    + ")";
+        }
+    }
+
+    /**
+     * A data class to contain whole component of {@link SatelliteIncomingDatagram} atom.
+     * Refer to {@link #onSatelliteIncomingDatagramMetrics(SatelliteIncomingDatagramParams)}.
+     */
+    public class SatelliteIncomingDatagramParams {
+        private final int mResultCode;
+        private final int mDatagramSizeBytes;
+        private final long mDatagramTransferTimeMillis;
+
+        private SatelliteIncomingDatagramParams(Builder builder) {
+            this.mResultCode = builder.mResultCode;
+            this.mDatagramSizeBytes = builder.mDatagramSizeBytes;
+            this.mDatagramTransferTimeMillis = builder.mDatagramTransferTimeMillis;
+        }
+
+        public int getResultCode() {
+            return mResultCode;
+        }
+
+        public int getDatagramSizeBytes() {
+            return mDatagramSizeBytes;
+        }
+
+        public long getDatagramTransferTimeMillis() {
+            return mDatagramTransferTimeMillis;
+        }
+
+        /**
+         * A builder class to create {@link SatelliteIncomingDatagramParams} data structure class
+         */
+        public static class Builder {
+            private int mResultCode = -1;
+            private int mDatagramSizeBytes = -1;
+            private long mDatagramTransferTimeMillis = -1;
+
+            /**
+             * Sets resultCode value of {@link SatelliteIncomingDatagram} atom
+             * then returns Builder class
+             */
+            public Builder setResultCode(int resultCode) {
+                this.mResultCode = resultCode;
+                return this;
+            }
+
+            /**
+             * Sets datagramSizeBytes value of {@link SatelliteIncomingDatagram} atom
+             * then returns Builder class
+             */
+            public Builder setDatagramSizeBytes(int datagramSizeBytes) {
+                this.mDatagramSizeBytes = datagramSizeBytes;
+                return this;
+            }
+
+            /**
+             * Sets datagramTransferTimeMillis value of {@link SatelliteIncomingDatagram} atom
+             * then returns Builder class
+             */
+            public Builder setDatagramTransferTimeMillis(long datagramTransferTimeMillis) {
+                this.mDatagramTransferTimeMillis = datagramTransferTimeMillis;
+                return this;
+            }
+
+            /**
+             * Returns IncomingDatagramParams, which contains whole component of
+             * {@link SatelliteIncomingDatagram} atom
+             */
+            public SatelliteIncomingDatagramParams build() {
+                return new SatelliteStats()
+                        .new SatelliteIncomingDatagramParams(Builder.this);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "IncomingDatagramParams("
+                    + ", resultCode=" + mResultCode
+                    + ", datagramSizeBytes=" + mDatagramSizeBytes
+                    + ", datagramTransferTimeMillis=" + mDatagramTransferTimeMillis + ")";
+        }
+    }
+
+    /**
+     * A data class to contain whole component of {@link SatelliteOutgoingDatagram} atom.
+     * Refer to {@link #onSatelliteOutgoingDatagramMetrics(SatelliteOutgoingDatagramParams)}.
+     */
+    public class SatelliteOutgoingDatagramParams {
+        private final int mDatagramType;
+        private final int mResultCode;
+        private final int mDatagramSizeBytes;
+        private final long mDatagramTransferTimeMillis;
+
+        private SatelliteOutgoingDatagramParams(Builder builder) {
+            this.mDatagramType = builder.mDatagramType;
+            this.mResultCode = builder.mResultCode;
+            this.mDatagramSizeBytes = builder.mDatagramSizeBytes;
+            this.mDatagramTransferTimeMillis = builder.mDatagramTransferTimeMillis;
+        }
+
+        public int getDatagramType() {
+            return mDatagramType;
+        }
+
+        public int getResultCode() {
+            return mResultCode;
+        }
+
+        public int getDatagramSizeBytes() {
+            return mDatagramSizeBytes;
+        }
+
+        public long getDatagramTransferTimeMillis() {
+            return mDatagramTransferTimeMillis;
+        }
+
+        /**
+         * A builder class to create {@link SatelliteOutgoingDatagramParams} data structure class
+         */
+        public static class Builder {
+            private int mDatagramType = -1;
+            private int mResultCode = -1;
+            private int mDatagramSizeBytes = -1;
+            private long mDatagramTransferTimeMillis = -1;
+
+            /**
+             * Sets datagramType value of {@link SatelliteOutgoingDatagram} atom
+             * then returns Builder class
+             */
+            public Builder setDatagramType(int datagramType) {
+                this.mDatagramType = datagramType;
+                return this;
+            }
+
+            /**
+             * Sets resultCode value of {@link SatelliteOutgoingDatagram} atom
+             * then returns Builder class
+             */
+            public Builder setResultCode(int resultCode) {
+                this.mResultCode = resultCode;
+                return this;
+            }
+
+            /**
+             * Sets datagramSizeBytes value of {@link SatelliteOutgoingDatagram} atom
+             * then returns Builder class
+             */
+            public Builder setDatagramSizeBytes(int datagramSizeBytes) {
+                this.mDatagramSizeBytes = datagramSizeBytes;
+                return this;
+            }
+
+            /**
+             * Sets datagramTransferTimeMillis value of {@link SatelliteOutgoingDatagram} atom
+             * then returns Builder class
+             */
+            public Builder setDatagramTransferTimeMillis(long datagramTransferTimeMillis) {
+                this.mDatagramTransferTimeMillis = datagramTransferTimeMillis;
+                return this;
+            }
+
+            /**
+             * Returns OutgoingDatagramParams, which contains whole component of
+             * {@link SatelliteOutgoingDatagram} atom
+             */
+            public SatelliteOutgoingDatagramParams build() {
+                return new SatelliteStats()
+                        .new SatelliteOutgoingDatagramParams(Builder.this);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "OutgoingDatagramParams("
+                    + "datagramType=" + mDatagramType
+                    + ", resultCode=" + mResultCode
+                    + ", datagramSizeBytes=" + mDatagramSizeBytes
+                    + ", datagramTransferTimeMillis=" + mDatagramTransferTimeMillis + ")";
+        }
+    }
+
+    /**
+     * A data class to contain whole component of {@link SatelliteProvision} atom.
+     * Refer to {@link #onSatelliteProvisionMetrics(SatelliteProvisionParams)}.
+     */
+    public class SatelliteProvisionParams {
+        private final int mResultCode;
+        private final int mProvisioningTimeSec;
+        private final boolean mIsProvisionRequest;
+        private final boolean mIsCanceled;
+
+        private SatelliteProvisionParams(Builder builder) {
+            this.mResultCode = builder.mResultCode;
+            this.mProvisioningTimeSec = builder.mProvisioningTimeSec;
+            this.mIsProvisionRequest = builder.mIsProvisionRequest;
+            this.mIsCanceled = builder.mIsCanceled;
+        }
+
+        public int getResultCode() {
+            return mResultCode;
+        }
+
+        public int getProvisioningTimeSec() {
+            return mProvisioningTimeSec;
+        }
+
+        public boolean getIsProvisionRequest() {
+            return mIsProvisionRequest;
+        }
+
+        public boolean getIsCanceled() {
+            return mIsCanceled;
+        }
+
+        /**
+         * A builder class to create {@link SatelliteProvisionParams} data structure class
+         */
+        public static class Builder {
+            private int mResultCode = -1;
+            private int mProvisioningTimeSec = -1;
+            private boolean mIsProvisionRequest = false;
+            private boolean mIsCanceled = false;
+
+            /**
+             * Sets resultCode value of {@link SatelliteProvision} atom
+             * then returns Builder class
+             */
+            public Builder setResultCode(int resultCode) {
+                this.mResultCode = resultCode;
+                return this;
+            }
+
+            /**
+             * Sets provisioningTimeSec value of {@link SatelliteProvision} atom
+             * then returns Builder class
+             */
+            public Builder setProvisioningTimeSec(int provisioningTimeSec) {
+                this.mProvisioningTimeSec = provisioningTimeSec;
+                return this;
+            }
+
+            /**
+             * Sets isProvisionRequest value of {@link SatelliteProvision} atom
+             * then returns Builder class
+             */
+            public Builder setIsProvisionRequest(boolean isProvisionRequest) {
+                this.mIsProvisionRequest = isProvisionRequest;
+                return this;
+            }
+
+            /**
+             * Sets isCanceled value of {@link SatelliteProvision} atom
+             * then returns Builder class
+             */
+            public Builder setIsCanceled(boolean isCanceled) {
+                this.mIsCanceled = isCanceled;
+                return this;
+            }
+
+            /**
+             * Returns ProvisionParams, which contains whole component of
+             * {@link SatelliteProvision} atom
+             */
+            public SatelliteProvisionParams build() {
+                return new SatelliteStats()
+                        .new SatelliteProvisionParams(Builder.this);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "ProvisionParams("
+                    + "resultCode=" + mResultCode
+                    + ", provisioningTimeSec=" + mProvisioningTimeSec
+                    + ", isProvisionRequest=" + mIsProvisionRequest
+                    + ", isCanceled" + mIsCanceled + ")";
+        }
+    }
+
+    /**
+     * A data class to contain whole component of {@link SatelliteSosMessageRecommender} atom.
+     * Refer to {@link #onSatelliteSosMessageRecommender(SatelliteSosMessageRecommenderParams)}.
+     */
+    public class SatelliteSosMessageRecommenderParams {
+        private final boolean mIsDisplaySosMessageSent;
+        private final int mCountOfTimerStarted;
+        private final boolean mIsImsRegistered;
+        private final int mCellularServiceState;
+
+        private SatelliteSosMessageRecommenderParams(Builder builder) {
+            this.mIsDisplaySosMessageSent = builder.mIsDisplaySosMessageSent;
+            this.mCountOfTimerStarted = builder.mCountOfTimerStarted;
+            this.mIsImsRegistered = builder.mIsImsRegistered;
+            this.mCellularServiceState = builder.mCellularServiceState;
+        }
+
+        public boolean isDisplaySosMessageSent() {
+            return mIsDisplaySosMessageSent;
+        }
+
+        public int getCountOfTimerStarted() {
+            return mCountOfTimerStarted;
+        }
+
+        public boolean isImsRegistered() {
+            return mIsImsRegistered;
+        }
+
+        public int getCellularServiceState() {
+            return mCellularServiceState;
+        }
+
+        /**
+         * A builder class to create {@link SatelliteProvisionParams} data structure class
+         */
+        public static class Builder {
+            private boolean mIsDisplaySosMessageSent = false;
+            private int mCountOfTimerStarted = -1;
+            private boolean mIsImsRegistered = false;
+            private int mCellularServiceState = -1;
+
+            /**
+             * Sets resultCode value of {@link SatelliteSosMessageRecommender} atom
+             * then returns Builder class
+             */
+            public Builder setDisplaySosMessageSent(
+                    boolean isDisplaySosMessageSent) {
+                this.mIsDisplaySosMessageSent = isDisplaySosMessageSent;
+                return this;
+            }
+
+            /**
+             * Sets countOfTimerIsStarted value of {@link SatelliteSosMessageRecommender} atom
+             * then returns Builder class
+             */
+            public Builder setCountOfTimerStarted(int countOfTimerStarted) {
+                this.mCountOfTimerStarted = countOfTimerStarted;
+                return this;
+            }
+
+            /**
+             * Sets isImsRegistered value of {@link SatelliteSosMessageRecommender} atom
+             * then returns Builder class
+             */
+            public Builder setImsRegistered(boolean isImsRegistered) {
+                this.mIsImsRegistered = isImsRegistered;
+                return this;
+            }
+
+            /**
+             * Sets cellularServiceState value of {@link SatelliteSosMessageRecommender} atom
+             * then returns Builder class
+             */
+            public Builder setCellularServiceState(int cellularServiceState) {
+                this.mCellularServiceState = cellularServiceState;
+                return this;
+            }
+
+            /**
+             * Returns SosMessageRecommenderParams, which contains whole component of
+             * {@link SatelliteSosMessageRecommenderParams} atom
+             */
+            public SatelliteSosMessageRecommenderParams build() {
+                return new SatelliteStats()
+                        .new SatelliteSosMessageRecommenderParams(Builder.this);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "SosMessageRecommenderParams("
+                    + "isDisplaySosMessageSent=" + mIsDisplaySosMessageSent
+                    + ", countOfTimerStarted=" + mCountOfTimerStarted
+                    + ", isImsRegistered=" + mIsImsRegistered
+                    + ", cellularServiceState=" + mCellularServiceState + ")";
+        }
+    }
+
+    /**  Create a new atom or update an existing atom for SatelliteController metrics */
+    public synchronized void onSatelliteControllerMetrics(SatelliteControllerParams param) {
+        SatelliteController proto = new SatelliteController();
+        proto.countOfSatelliteServiceEnablementsSuccess =
+                param.getCountOfSatelliteServiceEnablementsSuccess();
+        proto.countOfSatelliteServiceEnablementsFail =
+                param.getCountOfSatelliteServiceEnablementsFail();
+        proto.countOfOutgoingDatagramSuccess = param.getCountOfOutgoingDatagramSuccess();
+        proto.countOfOutgoingDatagramFail = param.getCountOfOutgoingDatagramFail();
+        proto.countOfIncomingDatagramSuccess = param.getCountOfIncomingDatagramSuccess();
+        proto.countOfIncomingDatagramFail = param.getCountOfIncomingDatagramFail();
+        proto.countOfDatagramTypeSosSmsSuccess = param.getCountOfDatagramTypeSosSmsSuccess();
+        proto.countOfDatagramTypeSosSmsFail = param.getCountOfDatagramTypeSosSmsFail();
+        proto.countOfDatagramTypeLocationSharingSuccess =
+                param.getCountOfDatagramTypeLocationSharingSuccess();
+        proto.countOfDatagramTypeLocationSharingFail =
+                param.getCountOfDatagramTypeLocationSharingFail();
+        proto.countOfProvisionSuccess = param.getCountOfProvisionSuccess();
+        proto.countOfProvisionFail = param.getCountOfProvisionFail();
+        proto.countOfDeprovisionSuccess = param.getCountOfDeprovisionSuccess();
+        proto.countOfDeprovisionFail = param.getCountOfDeprovisionFail();
+        proto.totalServiceUptimeSec = param.getTotalServiceUptimeSec();
+        proto.totalBatteryConsumptionPercent = param.getTotalBatteryConsumptionPercent();
+        proto.totalBatteryChargedTimeSec = param.getTotalBatteryChargedTimeSec();
+
+        mAtomsStorage.addSatelliteControllerStats(proto);
+    }
+
+    /**  Create a new atom or update an existing atom for SatelliteSession metrics */
+    public synchronized void onSatelliteSessionMetrics(SatelliteSessionParams param) {
+        SatelliteSession proto = new SatelliteSession();
+        proto.satelliteServiceInitializationResult =
+                param.getSatelliteServiceInitializationResult();
+        proto.satelliteTechnology = param.getSatelliteTechnology();
+        proto.count = 1;
+        mAtomsStorage.addSatelliteSessionStats(proto);
+    }
+
+    /**  Create a new atom for SatelliteIncomingDatagram metrics */
+    public synchronized void onSatelliteIncomingDatagramMetrics(
+            SatelliteIncomingDatagramParams param) {
+        SatelliteIncomingDatagram proto = new SatelliteIncomingDatagram();
+        proto.resultCode = param.getResultCode();
+        proto.datagramSizeBytes = param.getDatagramSizeBytes();
+        proto.datagramTransferTimeMillis = param.getDatagramTransferTimeMillis();
+        mAtomsStorage.addSatelliteIncomingDatagramStats(proto);
+    }
+
+    /**  Create a new atom for SatelliteOutgoingDatagram metrics */
+    public synchronized void onSatelliteOutgoingDatagramMetrics(
+            SatelliteOutgoingDatagramParams param) {
+        SatelliteOutgoingDatagram proto = new SatelliteOutgoingDatagram();
+        proto.datagramType = param.getDatagramType();
+        proto.resultCode = param.getResultCode();
+        proto.datagramSizeBytes = param.getDatagramSizeBytes();
+        proto.datagramTransferTimeMillis = param.getDatagramTransferTimeMillis();
+        mAtomsStorage.addSatelliteOutgoingDatagramStats(proto);
+    }
+
+    /**  Create a new atom for SatelliteProvision metrics */
+    public synchronized void onSatelliteProvisionMetrics(SatelliteProvisionParams param) {
+        SatelliteProvision proto = new SatelliteProvision();
+        proto.resultCode = param.getResultCode();
+        proto.provisioningTimeSec = param.getProvisioningTimeSec();
+        proto.isProvisionRequest = param.getIsProvisionRequest();
+        proto.isCanceled = param.getIsCanceled();
+        mAtomsStorage.addSatelliteProvisionStats(proto);
+    }
+
+    /**  Create a new atom or update an existing atom for SatelliteSosMessageRecommender metrics */
+    public synchronized void onSatelliteSosMessageRecommender(
+            SatelliteSosMessageRecommenderParams param) {
+        SatelliteSosMessageRecommender proto = new SatelliteSosMessageRecommender();
+        proto.isDisplaySosMessageSent = param.isDisplaySosMessageSent();
+        proto.countOfTimerStarted = param.getCountOfTimerStarted();
+        proto.isImsRegistered = param.isImsRegistered();
+        proto.cellularServiceState = param.getCellularServiceState();
+        proto.count = 1;
+        mAtomsStorage.addSatelliteSosMessageRecommenderStats(proto);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java b/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java
index 9d9088e..b830cd0 100644
--- a/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java
+++ b/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java
@@ -15,7 +15,13 @@
  */
 
 package com.android.internal.telephony.metrics;
+import static android.telephony.TelephonyManager.DATA_CONNECTED;
 
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_UNKNOWN;
+
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.SystemClock;
 import android.telephony.AccessNetworkConstants;
@@ -25,33 +31,36 @@
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 
-import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
-import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
-import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_UNKNOWN;
-
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.ServiceStateTracker;
+import com.android.internal.telephony.data.DataNetwork;
+import com.android.internal.telephony.data.DataNetworkController;
+import com.android.internal.telephony.data.DataNetworkController.DataNetworkControllerCallback;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState;
 import com.android.telephony.Rlog;
 
+import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
 
 /** Tracks service state duration and switch metrics for each phone. */
-public class ServiceStateStats {
+public class ServiceStateStats extends DataNetworkControllerCallback {
     private static final String TAG = ServiceStateStats.class.getSimpleName();
 
     private final AtomicReference<TimestampedServiceState> mLastState =
             new AtomicReference<>(new TimestampedServiceState(null, 0L));
     private final Phone mPhone;
     private final PersistAtomsStorage mStorage;
+    private final DeviceStateHelper mDeviceStateHelper;
 
     public ServiceStateStats(Phone phone) {
+        super(Runnable::run);
         mPhone = phone;
         mStorage = PhoneFactory.getMetricsCollector().getAtomsStorage();
+        mDeviceStateHelper = PhoneFactory.getMetricsCollector().getDeviceStateHelper();
     }
 
     /** Finalizes the durations of the current service state segment. */
@@ -80,6 +89,21 @@
         addServiceState(lastState, now);
     }
 
+    /** Registers for internet pdn connected callback. */
+    public void registerDataNetworkControllerCallback() {
+        mPhone.getDataNetworkController().registerDataNetworkControllerCallback(this);
+    }
+
+    /** Updates service state when internet pdn gets connected. */
+    public void onInternetDataNetworkConnected(@NonNull List<DataNetwork> internetNetworks) {
+        onInternetDataNetworkChanged(true);
+    }
+
+    /** Updates service state when internet pdn gets disconnected. */
+    public void onInternetDataNetworkDisconnected() {
+        onInternetDataNetworkChanged(false);
+    }
+
     /** Updates the current service state. */
     public void onServiceStateChanged(ServiceState serviceState) {
         final long now = getTimeMillis();
@@ -97,7 +121,8 @@
             newState.isMultiSim = SimSlotState.isMultiSim();
             newState.carrierId = mPhone.getCarrierId();
             newState.isEmergencyOnly = isEmergencyOnly(serviceState);
-
+            newState.isInternetPdnUp = isInternetPdnUp(mPhone);
+            newState.foldState = mDeviceStateHelper.getFoldState();
             TimestampedServiceState prevState =
                     mLastState.getAndSet(new TimestampedServiceState(newState, now));
             addServiceStateAndSwitch(
@@ -105,6 +130,26 @@
         }
     }
 
+    /** Updates the fold state of the device for the current service state. */
+    public void onFoldStateChanged(int foldState) {
+        final long now = getTimeMillis();
+        CellularServiceState lastServiceState = mLastState.get().mServiceState;
+        if (lastServiceState == null || lastServiceState.foldState == foldState) {
+            // Not need to update the fold state if modem is off or if is the
+            // same fold state
+            return;
+        } else {
+            TimestampedServiceState lastState =
+                    mLastState.getAndUpdate(
+                            state -> {
+                                CellularServiceState newServiceState = copyOf(state.mServiceState);
+                                newServiceState.foldState = foldState;
+                                return new TimestampedServiceState(newServiceState, now);
+                            });
+            addServiceState(lastState, now);
+        }
+    }
+
     private void addServiceState(TimestampedServiceState prevState, long now) {
         addServiceStateAndSwitch(prevState, now, null);
     }
@@ -224,6 +269,8 @@
         copy.carrierId = state.carrierId;
         copy.totalTimeMillis = state.totalTimeMillis;
         copy.isEmergencyOnly = state.isEmergencyOnly;
+        copy.isInternetPdnUp = state.isInternetPdnUp;
+        copy.foldState = state.foldState;
         return copy;
     }
 
@@ -310,6 +357,29 @@
                 || nrState == NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED;
     }
 
+    private static boolean isInternetPdnUp(Phone phone) {
+        DataNetworkController dataNetworkController = phone.getDataNetworkController();
+        if (dataNetworkController != null) {
+            return dataNetworkController.getInternetDataNetworkState() == DATA_CONNECTED;
+        }
+        return false;
+    }
+
+    private void onInternetDataNetworkChanged(boolean internetPdnUp) {
+        final long now = getTimeMillis();
+        TimestampedServiceState lastState =
+                mLastState.getAndUpdate(
+                        state -> {
+                            if (state.mServiceState == null) {
+                                return new TimestampedServiceState(null, now);
+                            }
+                            CellularServiceState newServiceState = copyOf(state.mServiceState);
+                            newServiceState.isInternetPdnUp = internetPdnUp;
+                            return new TimestampedServiceState(newServiceState, now);
+                        });
+        addServiceState(lastState, now);
+    }
+
     @VisibleForTesting
     protected long getTimeMillis() {
         return SystemClock.elapsedRealtime();
diff --git a/src/java/com/android/internal/telephony/metrics/SmsStats.java b/src/java/com/android/internal/telephony/metrics/SmsStats.java
index 48826fd..2f1e6a7 100644
--- a/src/java/com/android/internal/telephony/metrics/SmsStats.java
+++ b/src/java/com/android/internal/telephony/metrics/SmsStats.java
@@ -58,6 +58,7 @@
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.ServiceStateTracker;
 import com.android.internal.telephony.nano.PersistAtomsProto.IncomingSms;
+import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingShortCodeSms;
 import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingSms;
 import com.android.telephony.Rlog;
 
@@ -155,46 +156,62 @@
 
     /** Create a new atom when an outgoing SMS is sent. */
     public void onOutgoingSms(boolean isOverIms, boolean is3gpp2, boolean fallbackToCs,
-            @SmsManager.Result int errorCode, long messageId, boolean isFromDefaultApp,
+            @SmsManager.Result int sendErrorCode, long messageId, boolean isFromDefaultApp,
             long intervalMillis) {
-        onOutgoingSms(isOverIms, is3gpp2, fallbackToCs, errorCode, NO_ERROR_CODE,
+        onOutgoingSms(isOverIms, is3gpp2, fallbackToCs, sendErrorCode, NO_ERROR_CODE,
                 messageId, isFromDefaultApp, intervalMillis);
     }
 
     /** Create a new atom when an outgoing SMS is sent. */
     public void onOutgoingSms(boolean isOverIms, boolean is3gpp2, boolean fallbackToCs,
-            @SmsManager.Result int errorCode, int radioSpecificErrorCode, long messageId,
+            @SmsManager.Result int sendErrorCode, int networkErrorCode, long messageId,
             boolean isFromDefaultApp, long intervalMillis) {
         OutgoingSms proto =
                 getOutgoingDefaultProto(is3gpp2, isOverIms, messageId, isFromDefaultApp,
                         intervalMillis);
 
+        // The field errorCode is used for up-to-Android-13 devices. From Android 14, sendErrorCode
+        // and networkErrorCode will be used. The field errorCode will be deprecated when most
+        // devices use Android 14 or higher versions.
         if (isOverIms) {
             // Populate error code and result for IMS case
-            proto.errorCode = errorCode;
+            proto.errorCode = sendErrorCode;
             if (fallbackToCs) {
                 proto.sendResult = OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR_FALLBACK;
-            } else if (errorCode == SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY) {
+            } else if (sendErrorCode == SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY) {
                 proto.sendResult = OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR_RETRY;
-            } else if (errorCode != SmsManager.RESULT_ERROR_NONE) {
+            } else if (sendErrorCode != SmsManager.RESULT_ERROR_NONE) {
                 proto.sendResult = OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR;
             }
         } else {
             // Populate error code and result for CS case
-            if (errorCode == SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY) {
+            if (sendErrorCode == SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY) {
                 proto.sendResult = OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR_RETRY;
-            } else if (errorCode != SmsManager.RESULT_ERROR_NONE) {
+            } else if (sendErrorCode != SmsManager.RESULT_ERROR_NONE) {
                 proto.sendResult = OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR;
             }
-            proto.errorCode = radioSpecificErrorCode;
-            if (errorCode == SmsManager.RESULT_RIL_RADIO_NOT_AVAILABLE
-                    && radioSpecificErrorCode == NO_ERROR_CODE) {
+            proto.errorCode = networkErrorCode;
+            if (sendErrorCode == SmsManager.RESULT_RIL_RADIO_NOT_AVAILABLE
+                    && networkErrorCode == NO_ERROR_CODE) {
                 proto.errorCode = is3gpp2 ? NO_NETWORK_ERROR_3GPP2 : NO_NETWORK_ERROR_3GPP;
             }
         }
+
+        proto.sendErrorCode = sendErrorCode;
+        proto.networkErrorCode = networkErrorCode;
+
         mAtomsStorage.addOutgoingSms(proto);
     }
 
+    /** Create a new atom when user attempted to send an outgoing short code sms. */
+    public void onOutgoingShortCodeSms(int category, int xmlVersion) {
+        OutgoingShortCodeSms proto = new OutgoingShortCodeSms();
+        proto.category = category;
+        proto.xmlVersion = xmlVersion;
+        proto.shortCodeSmsCount = 1;
+        mAtomsStorage.addOutgoingShortCodeSms(proto);
+    }
+
     /** Creates a proto for a normal single-part {@code IncomingSms} with default values. */
     private IncomingSms getIncomingDefaultProto(boolean is3gpp2,
             @InboundSmsHandler.SmsSource int smsSource) {
@@ -216,6 +233,7 @@
         // SMS messages (e.g. those handled by OS or error cases).
         proto.messageId = RANDOM.nextLong();
         proto.count = 1;
+        proto.isManagedProfile = mPhone.isManagedProfile();
         return proto;
     }
 
@@ -241,6 +259,7 @@
         proto.retryId = 0;
         proto.intervalMillis = intervalMillis;
         proto.count = 1;
+        proto.isManagedProfile = mPhone.isManagedProfile();
         return proto;
     }
 
diff --git a/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java b/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java
index 7d68ae1..ba07fa0 100644
--- a/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java
+++ b/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java
@@ -46,6 +46,7 @@
 import android.telephony.Annotation.NetworkType;
 import android.telephony.AnomalyReporter;
 import android.telephony.DisconnectCause;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsReasonInfo;
@@ -63,6 +64,7 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.ServiceStateTracker;
+import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneConnection;
 import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession;
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyCallSession.Event.AudioCodec;
@@ -155,6 +157,8 @@
     private final PersistAtomsStorage mAtomsStorage =
             PhoneFactory.getMetricsCollector().getAtomsStorage();
     private final UiccController mUiccController = UiccController.getInstance();
+    private final DeviceStateHelper mDeviceStateHelper =
+            PhoneFactory.getMetricsCollector().getDeviceStateHelper();
 
     public VoiceCallSessionStats(int phoneId, Phone phone) {
         mPhoneId = phoneId;
@@ -366,9 +370,8 @@
                     proto.srvccCompleted = true;
                     proto.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
                     // Call RAT may have changed (e.g. IWLAN -> UMTS) due to bearer change
-                    proto.ratAtEnd =
-                            ServiceStateStats.getVoiceRat(
-                                    mPhone, mPhone.getServiceState(), proto.bearerAtEnd);
+                    updateRatAtEnd(proto, getVoiceRatWithVoNRFix(
+                            mPhone, mPhone.getServiceState(), proto.bearerAtEnd));
                 }
                 break;
             case TelephonyManager.SRVCC_STATE_HANDOVER_FAILED:
@@ -422,8 +425,7 @@
         }
         int bearer = getBearer(conn);
         ServiceState serviceState = getServiceState();
-        @NetworkType int rat = ServiceStateStats.getVoiceRat(mPhone, serviceState, bearer);
-
+        @NetworkType int rat = getVoiceRatWithVoNRFix(mPhone, serviceState, bearer);
         VoiceCallSession proto = new VoiceCallSession();
 
         proto.bearerAtStart = bearer;
@@ -446,7 +448,7 @@
         proto.srvccFailureCount = 0L;
         proto.srvccCancellationCount = 0L;
         proto.rttEnabled = false;
-        proto.isEmergency = conn.isEmergencyCall();
+        proto.isEmergency = conn.isEmergencyCall() || conn.isNetworkIdentifiedEmergencyCall();
         proto.isRoaming = serviceState != null ? serviceState.getVoiceRoaming() : false;
         proto.isMultiparty = conn.isMultiparty();
         proto.lastKnownRat = rat;
@@ -512,15 +514,10 @@
         }
 
         // Update end RAT
-        @NetworkType
-        int rat = ServiceStateStats.getVoiceRat(mPhone, getServiceState(), proto.bearerAtEnd);
-        if (proto.ratAtEnd != rat) {
-            proto.ratSwitchCount++;
-            proto.ratAtEnd = rat;
-            if (rat != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
-                proto.lastKnownRat = rat;
-            }
-        }
+        updateRatAtEnd(proto, getVoiceRatWithVoNRFix(mPhone, getServiceState(), proto.bearerAtEnd));
+
+        // Set device fold state
+        proto.foldState = mDeviceStateHelper.getFoldState();
 
         mAtomsStorage.addVoiceCallSession(proto);
 
@@ -581,8 +578,7 @@
             proto.setupFailed = false;
             // Track RAT when voice call is connected.
             ServiceState serviceState = getServiceState();
-            proto.ratAtConnected =
-                    ServiceStateStats.getVoiceRat(mPhone, serviceState, proto.bearerAtEnd);
+            proto.ratAtConnected = getVoiceRatWithVoNRFix(mPhone, serviceState, proto.bearerAtEnd);
             // Reset list of codecs with the last codec at the present time. In this way, we
             // track codec quality only after call is connected and not while ringing.
             resetCodecList(conn);
@@ -593,19 +589,14 @@
         // RAT usage is not broken down by bearer. In case a CS call is made while there is IMS
         // voice registration, this may be inaccurate (i.e. there could be multiple RAT in use, but
         // we only pick the most feasible one).
-        @NetworkType int rat = ServiceStateStats.getVoiceRat(mPhone, state);
+        @NetworkType int rat = getVoiceRatWithVoNRFix(mPhone, state,
+                VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_UNKNOWN);
         mRatUsage.add(mPhone.getCarrierId(), rat, getTimeMillis(), getConnectionIds());
 
         for (int i = 0; i < mCallProtos.size(); i++) {
             VoiceCallSession proto = mCallProtos.valueAt(i);
-            rat = ServiceStateStats.getVoiceRat(mPhone, state, proto.bearerAtEnd);
-            if (proto.ratAtEnd != rat) {
-                proto.ratSwitchCount++;
-                proto.ratAtEnd = rat;
-                if (rat != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
-                    proto.lastKnownRat = rat;
-                }
-            }
+            rat = getVoiceRatWithVoNRFix(mPhone, state, proto.bearerAtEnd);
+            updateRatAtEnd(proto, rat);
             proto.bandAtEnd = (rat == TelephonyManager.NETWORK_TYPE_IWLAN)
                             ? 0
                             : ServiceStateStats.getBand(state);
@@ -613,6 +604,16 @@
         }
     }
 
+    private void updateRatAtEnd(VoiceCallSession proto, @NetworkType int rat) {
+        if (proto.ratAtEnd != rat) {
+            proto.ratSwitchCount++;
+            proto.ratAtEnd = rat;
+            if (rat != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
+                proto.lastKnownRat = rat;
+            }
+        }
+    }
+
     private void finishImsCall(int id, ImsReasonInfo reasonInfo, long durationMillis) {
         VoiceCallSession proto = mCallProtos.get(id);
         proto.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
@@ -679,6 +680,43 @@
         return mPhone.getSignalStrength().getLevel();
     }
 
+    /**
+     * This is a copy of ServiceStateStats.getVoiceRat(Phone, ServiceState, int) with minimum fix
+     * required for tracking EPSFB correctly.
+     */
+    @VisibleForTesting private static @NetworkType int getVoiceRatWithVoNRFix(
+            Phone phone, @Nullable ServiceState state, int bearer) {
+        if (state == null) {
+            return TelephonyManager.NETWORK_TYPE_UNKNOWN;
+        }
+        ImsPhone imsPhone = (ImsPhone) phone.getImsPhone();
+        if (bearer != VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS && imsPhone != null) {
+            @NetworkType int imsVoiceRat = imsPhone.getImsStats().getImsVoiceRadioTech();
+            @NetworkType int wwanPsRat =
+                    ServiceStateStats.getRat(state, NetworkRegistrationInfo.DOMAIN_PS);
+            if (imsVoiceRat != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
+                // If IMS is registered over WWAN but WWAN PS is not in service,
+                // fallback to WWAN CS RAT
+                boolean isImsVoiceRatValid =
+                        (imsVoiceRat == TelephonyManager.NETWORK_TYPE_IWLAN
+                                || wwanPsRat != TelephonyManager.NETWORK_TYPE_UNKNOWN);
+                if (isImsVoiceRatValid) {
+                    // Fix for VoNR and EPSFB, b/277906557
+                    @NetworkType int oldRat = ServiceStateStats.getVoiceRat(phone, state, bearer),
+                            rat = imsVoiceRat == TelephonyManager.NETWORK_TYPE_IWLAN
+                                    ? imsVoiceRat : wwanPsRat;
+                    logd("getVoiceRatWithVoNRFix: oldRat=%d, newRat=%d", oldRat, rat);
+                    return rat;
+                }
+            }
+        }
+        if (bearer == VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS) {
+            return TelephonyManager.NETWORK_TYPE_UNKNOWN;
+        } else {
+            return ServiceStateStats.getRat(state, NetworkRegistrationInfo.DOMAIN_CS);
+        }
+    }
+
     /** Resets the list of codecs used for the connection with only the codec currently in use. */
     private void resetCodecList(Connection conn) {
         int id = getConnectionId(conn);
diff --git a/src/java/com/android/internal/telephony/nitz/NitzStateMachineImpl.java b/src/java/com/android/internal/telephony/nitz/NitzStateMachineImpl.java
index 8115127..8b34933 100644
--- a/src/java/com/android/internal/telephony/nitz/NitzStateMachineImpl.java
+++ b/src/java/com/android/internal/telephony/nitz/NitzStateMachineImpl.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.time.UnixEpochTime;
 import android.app.timedetector.TelephonyTimeSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 import android.content.Context;
@@ -372,7 +373,7 @@
                 builder.addDebugInfo("Clearing time suggestion"
                         + " reason=" + reason);
             } else {
-                TimestampedValue<Long> newNitzTime = nitzSignal.createTimeSignal();
+                UnixEpochTime newNitzTime = nitzSignal.createTimeSignal();
                 builder.setUnixEpochTime(newNitzTime);
                 builder.addDebugInfo("Sending new time suggestion"
                         + " nitzSignal=" + nitzSignal
diff --git a/src/java/com/android/internal/telephony/nitz/TimeServiceHelperImpl.java b/src/java/com/android/internal/telephony/nitz/TimeServiceHelperImpl.java
index 9c7aac9..74b30f8 100644
--- a/src/java/com/android/internal/telephony/nitz/TimeServiceHelperImpl.java
+++ b/src/java/com/android/internal/telephony/nitz/TimeServiceHelperImpl.java
@@ -18,13 +18,13 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.time.UnixEpochTime;
 import android.app.timedetector.TelephonyTimeSuggestion;
 import android.app.timedetector.TimeDetector;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 import android.app.timezonedetector.TimeZoneDetector;
 import android.content.Context;
 import android.os.SystemClock;
-import android.os.TimestampedValue;
 import android.util.LocalLog;
 
 import com.android.internal.telephony.Phone;
@@ -69,8 +69,9 @@
         Objects.requireNonNull(timeSuggestion);
 
         if (timeSuggestion.getUnixEpochTime() != null) {
-            TimestampedValue<Long> unixEpochTime = timeSuggestion.getUnixEpochTime();
-            TelephonyMetrics.getInstance().writeNITZEvent(mSlotIndex, unixEpochTime.getValue());
+            UnixEpochTime unixEpochTime = timeSuggestion.getUnixEpochTime();
+            TelephonyMetrics.getInstance().writeNITZEvent(
+                    mSlotIndex, unixEpochTime.getUnixEpochTimeMillis());
         }
         mTimeDetector.suggestTelephonyTime(timeSuggestion);
     }
diff --git a/src/java/com/android/internal/telephony/satellite/DatagramController.java b/src/java/com/android/internal/telephony/satellite/DatagramController.java
new file mode 100644
index 0000000..e7f09c2
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/DatagramController.java
@@ -0,0 +1,379 @@
+/*
+ * 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.satellite;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.Build;
+import android.os.Looper;
+import android.os.SystemProperties;
+import android.telephony.Rlog;
+import android.telephony.satellite.ISatelliteDatagramCallback;
+import android.telephony.satellite.SatelliteDatagram;
+import android.telephony.satellite.SatelliteManager;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * Datagram controller used for sending and receiving satellite datagrams.
+ */
+public class DatagramController {
+    private static final String TAG = "DatagramController";
+
+    @NonNull private static DatagramController sInstance;
+    @NonNull private final Context mContext;
+    @NonNull private final PointingAppController mPointingAppController;
+    @NonNull private final DatagramDispatcher mDatagramDispatcher;
+    @NonNull private final DatagramReceiver mDatagramReceiver;
+    public static final long MAX_DATAGRAM_ID = (long) Math.pow(2, 16);
+    public static final int ROUNDING_UNIT = 10;
+    public static final long SATELLITE_ALIGN_TIMEOUT = TimeUnit.SECONDS.toMillis(30);
+    private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem";
+    private static final boolean DEBUG = !"user".equals(Build.TYPE);
+
+    /** Variables used to update onSendDatagramStateChanged(). */
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private int mSendSubId;
+    @GuardedBy("mLock")
+    private @SatelliteManager.SatelliteDatagramTransferState int mSendDatagramTransferState =
+            SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE;
+    @GuardedBy("mLock")
+    private int mSendPendingCount = 0;
+    @GuardedBy("mLock")
+    private int mSendErrorCode = SatelliteManager.SATELLITE_ERROR_NONE;
+    /** Variables used to update onReceiveDatagramStateChanged(). */
+    @GuardedBy("mLock")
+    private int mReceiveSubId;
+    @GuardedBy("mLock")
+    private @SatelliteManager.SatelliteDatagramTransferState int mReceiveDatagramTransferState =
+            SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE;
+    @GuardedBy("mLock")
+    private int mReceivePendingCount = 0;
+    @GuardedBy("mLock")
+    private int mReceiveErrorCode = SatelliteManager.SATELLITE_ERROR_NONE;
+
+    private SatelliteDatagram mDemoModeDatagram;
+    private boolean mIsDemoMode = false;
+    private long mAlignTimeoutDuration = SATELLITE_ALIGN_TIMEOUT;
+
+    /**
+     * @return The singleton instance of DatagramController.
+     */
+    public static DatagramController getInstance() {
+        if (sInstance == null) {
+            loge("DatagramController was not yet initialized.");
+        }
+        return sInstance;
+    }
+
+    /**
+     * Create the DatagramController singleton instance.
+     * @param context The Context to use to create the DatagramController.
+     * @param looper The looper for the handler.
+     * @param pointingAppController PointingAppController is used to update
+     *                              PointingApp about datagram transfer state changes.
+     * @return The singleton instance of DatagramController.
+     */
+    public static DatagramController make(@NonNull Context context, @NonNull Looper looper,
+            @NonNull PointingAppController pointingAppController) {
+        if (sInstance == null) {
+            sInstance = new DatagramController(context, looper, pointingAppController);
+        }
+        return sInstance;
+    }
+
+    /**
+     * Create a DatagramController to send and receive satellite datagrams.
+     *
+     * @param context The Context for the DatagramController.
+     * @param looper The looper for the handler
+     * @param pointingAppController PointingAppController is used to update PointingApp
+     *                              about datagram transfer state changes.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected DatagramController(@NonNull Context context, @NonNull Looper  looper,
+            @NonNull PointingAppController pointingAppController) {
+        mContext = context;
+        mPointingAppController = pointingAppController;
+
+        // Create the DatagramDispatcher singleton,
+        // which is used to send satellite datagrams.
+        mDatagramDispatcher = DatagramDispatcher.make(mContext, looper, this);
+
+        // Create the DatagramReceiver singleton,
+        // which is used to receive satellite datagrams.
+        mDatagramReceiver = DatagramReceiver.make(mContext, looper, this);
+    }
+
+    /**
+     * Register to receive incoming datagrams over satellite.
+     *
+     * @param subId The subId of the subscription to register for incoming satellite datagrams.
+     * @param callback The callback to handle incoming datagrams over satellite.
+     *
+     * @return The {@link SatelliteManager.SatelliteError} result of the operation.
+     */
+    @SatelliteManager.SatelliteError public int registerForSatelliteDatagram(int subId,
+            @NonNull ISatelliteDatagramCallback callback) {
+        return mDatagramReceiver.registerForSatelliteDatagram(subId, callback);
+    }
+
+    /**
+     * Unregister to stop receiving incoming datagrams over satellite.
+     * If callback was not registered before, the request will be ignored.
+     *
+     * @param subId The subId of the subscription to unregister for incoming satellite datagrams.
+     * @param callback The callback that was passed to
+     *                 {@link #registerForSatelliteDatagram(int, ISatelliteDatagramCallback)}.
+     */
+    public void unregisterForSatelliteDatagram(int subId,
+            @NonNull ISatelliteDatagramCallback callback) {
+        mDatagramReceiver.unregisterForSatelliteDatagram(subId, callback);
+    }
+
+    /**
+     * Poll pending satellite datagrams over satellite.
+     *
+     * This method requests modem to check if there are any pending datagrams to be received over
+     * satellite. If there are any incoming datagrams, they will be received via
+     * {@link android.telephony.satellite.SatelliteDatagramCallback#onSatelliteDatagramReceived(
+     * long, SatelliteDatagram, int, Consumer)}
+     *
+     * @param subId The subId of the subscription used for receiving datagrams.
+     * @param callback The callback to get {@link SatelliteManager.SatelliteError} of the request.
+     */
+    public void pollPendingSatelliteDatagrams(int subId, @NonNull Consumer<Integer> callback) {
+        mDatagramReceiver.pollPendingSatelliteDatagrams(subId, callback);
+    }
+
+    /**
+     * Send datagram over satellite.
+     *
+     * Gateway encodes SOS message or location sharing message into a datagram and passes it as
+     * input to this method. Datagram received here will be passed down to modem without any
+     * encoding or encryption.
+     *
+     * When demo mode is on, save the sent datagram and this datagram will be used as a received
+     * datagram.
+     *
+     * @param subId The subId of the subscription to send satellite datagrams for.
+     * @param datagramType datagram type indicating whether the datagram is of type
+     *                     SOS_SMS or LOCATION_SHARING.
+     * @param datagram encoded gateway datagram which is encrypted by the caller.
+     *                 Datagram will be passed down to modem without any encoding or encryption.
+     * @param needFullScreenPointingUI this is used to indicate pointingUI app to open in
+     *                                 full screen mode.
+     * @param callback The callback to get {@link SatelliteManager.SatelliteError} of the request.
+     */
+    public void sendSatelliteDatagram(int subId, @SatelliteManager.DatagramType int datagramType,
+            @NonNull SatelliteDatagram datagram, boolean needFullScreenPointingUI,
+            @NonNull Consumer<Integer> callback) {
+        setDemoModeDatagram(datagramType, datagram);
+        mDatagramDispatcher.sendSatelliteDatagram(subId, datagramType, datagram,
+                needFullScreenPointingUI, callback);
+    }
+
+    /**
+     * Update send status to {@link PointingAppController}.
+     *
+     * @param subId The subId of the subscription to send satellite datagrams for
+     * @param datagramTransferState The new send datagram transfer state.
+     * @param sendPendingCount number of datagrams that are currently being sent
+     * @param errorCode If datagram transfer failed, the reason for failure.
+     */
+    public void updateSendStatus(int subId,
+            @SatelliteManager.SatelliteDatagramTransferState int datagramTransferState,
+            int sendPendingCount, int errorCode) {
+        synchronized (mLock) {
+            logd("updateSendStatus"
+                    + " subId: " + subId
+                    + " datagramTransferState: " + datagramTransferState
+                    + " sendPendingCount: " + sendPendingCount + " errorCode: " + errorCode);
+
+            mSendSubId = subId;
+            mSendDatagramTransferState = datagramTransferState;
+            mSendPendingCount = sendPendingCount;
+            mSendErrorCode = errorCode;
+
+            notifyDatagramTransferStateChangedToSessionController();
+            mPointingAppController.updateSendDatagramTransferState(mSendSubId,
+                    mSendDatagramTransferState, mSendPendingCount, mSendErrorCode);
+        }
+    }
+
+    /**
+     * Update receive status to {@link PointingAppController}.
+     *
+     * @param subId The subId of the subscription used to receive datagrams
+     * @param datagramTransferState The new receive datagram transfer state.
+     * @param receivePendingCount The number of datagrams that are currently pending to be received.
+     * @param errorCode If datagram transfer failed, the reason for failure.
+     */
+    public void updateReceiveStatus(int subId,
+            @SatelliteManager.SatelliteDatagramTransferState int datagramTransferState,
+            int receivePendingCount, int errorCode) {
+        synchronized (mLock) {
+            logd("updateReceiveStatus"
+                    + " subId: " + subId
+                    + " datagramTransferState: " + datagramTransferState
+                    + " receivePendingCount: " + receivePendingCount + " errorCode: " + errorCode);
+
+            mReceiveSubId = subId;
+            mReceiveDatagramTransferState = datagramTransferState;
+            mReceivePendingCount = receivePendingCount;
+            mReceiveErrorCode = errorCode;
+
+            notifyDatagramTransferStateChangedToSessionController();
+            mPointingAppController.updateReceiveDatagramTransferState(mReceiveSubId,
+                    mReceiveDatagramTransferState, mReceivePendingCount, mReceiveErrorCode);
+        }
+
+        if (isPollingInIdleState()) {
+            mDatagramDispatcher.retrySendingDatagrams();
+        }
+    }
+
+    /**
+     * Return receive pending datagram count
+     * @return receive pending datagram count.
+     */
+    public int getReceivePendingCount() {
+        return mReceivePendingCount;
+    }
+
+    /**
+     * This function is used by {@link SatelliteController} to notify {@link DatagramController}
+     * that satellite modem state has changed.
+     *
+     * @param state Current satellite modem state.
+     */
+    public void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state) {
+        mDatagramDispatcher.onSatelliteModemStateChanged(state);
+        mDatagramReceiver.onSatelliteModemStateChanged(state);
+    }
+
+    void onDeviceAlignedWithSatellite(boolean isAligned) {
+        mDatagramDispatcher.onDeviceAlignedWithSatellite(isAligned);
+        mDatagramReceiver.onDeviceAlignedWithSatellite(isAligned);
+    }
+
+    @VisibleForTesting
+    public boolean isReceivingDatagrams() {
+        synchronized (mLock) {
+            return (mReceiveDatagramTransferState
+                    == SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING);
+        }
+    }
+
+    public boolean isSendingInIdleState() {
+        synchronized (mLock) {
+            return mSendDatagramTransferState ==
+                    SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE;
+        }
+    }
+
+    public boolean isPollingInIdleState() {
+        synchronized (mLock) {
+            return mReceiveDatagramTransferState ==
+                    SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE;
+        }
+    }
+
+    /**
+     * Set variables for {@link DatagramDispatcher} and {@link DatagramReceiver} to run demo mode
+     * @param isDemoMode {@code true} means demo mode is on, {@code false} otherwise.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void setDemoMode(boolean isDemoMode) {
+        mIsDemoMode = isDemoMode;
+        mDatagramDispatcher.setDemoMode(isDemoMode);
+        mDatagramReceiver.setDemoMode(isDemoMode);
+
+        if (!isDemoMode) {
+            mDemoModeDatagram = null;
+        }
+    }
+
+    /** Get the last sent datagram for demo mode */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public SatelliteDatagram getDemoModeDatagram() {
+        return mDemoModeDatagram;
+    }
+
+    /**
+     * Set last sent datagram for demo mode
+     * @param datagramType datagram type, only DATAGRAM_TYPE_SOS_MESSAGE will be saved
+     * @param datagram datagram The last datagram saved when sendSatelliteDatagramForDemo is called
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected void setDemoModeDatagram(@SatelliteManager.DatagramType int datagramType,
+            SatelliteDatagram datagram) {
+        if (mIsDemoMode &&  datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) {
+            mDemoModeDatagram = datagram;
+        }
+    }
+
+    long getSatelliteAlignedTimeoutDuration() {
+        return mAlignTimeoutDuration;
+    }
+
+    /**
+     * 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
+     *
+     * @param timeoutMillis The timeout duration in millisecond.
+     * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise.
+     */
+    boolean setSatelliteDeviceAlignedTimeoutDuration(long timeoutMillis) {
+        if (!isMockModemAllowed()) {
+            loge("Updating align timeout duration is not allowed");
+            return false;
+        }
+
+        logd("setSatelliteDeviceAlignedTimeoutDuration: timeoutMillis=" + timeoutMillis);
+        mAlignTimeoutDuration = timeoutMillis;
+        return true;
+    }
+
+    private boolean isMockModemAllowed() {
+        return (DEBUG || SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false));
+    }
+
+    private void notifyDatagramTransferStateChangedToSessionController() {
+        SatelliteSessionController sessionController = SatelliteSessionController.getInstance();
+        if (sessionController == null) {
+            loge("notifyDatagramTransferStateChangeToSessionController: SatelliteSessionController"
+                    + " is not initialized yet");
+        } else {
+            sessionController.onDatagramTransferStateChanged(
+                    mSendDatagramTransferState, mReceiveDatagramTransferState);
+        }
+    }
+
+    private static void logd(@NonNull String log) {
+        Rlog.d(TAG, log);
+    }
+
+    private static void loge(@NonNull String log) {
+        Rlog.e(TAG, log);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java b/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
new file mode 100644
index 0000000..77b410d
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
@@ -0,0 +1,615 @@
+/*
+ * 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.satellite;
+
+import static com.android.internal.telephony.satellite.DatagramController.ROUNDING_UNIT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
+import android.telephony.satellite.SatelliteDatagram;
+import android.telephony.satellite.SatelliteManager;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.metrics.SatelliteStats;
+import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats;
+
+import java.util.LinkedHashMap;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Consumer;
+
+/**
+ * Datagram dispatcher used to send satellite datagrams.
+ */
+public class DatagramDispatcher extends Handler {
+    private static final String TAG = "DatagramDispatcher";
+
+    private static final int CMD_SEND_SATELLITE_DATAGRAM = 1;
+    private static final int EVENT_SEND_SATELLITE_DATAGRAM_DONE = 2;
+    private static final int EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT = 3;
+
+    @NonNull private static DatagramDispatcher sInstance;
+    @NonNull private final Context mContext;
+    @NonNull private final DatagramController mDatagramController;
+    @NonNull private final ControllerMetricsStats mControllerMetricsStats;
+
+    private boolean mIsDemoMode = false;
+    private boolean mIsAligned = false;
+    private DatagramDispatcherHandlerRequest mSendSatelliteDatagramRequest = null;
+
+    private static AtomicLong mNextDatagramId = new AtomicLong(0);
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private boolean mSendingDatagramInProgress;
+
+    /**
+     * Map key: datagramId, value: SendSatelliteDatagramArgument to retry sending emergency
+     * datagrams.
+     */
+    @GuardedBy("mLock")
+    private final LinkedHashMap<Long, SendSatelliteDatagramArgument>
+            mPendingEmergencyDatagramsMap = new LinkedHashMap<>();
+
+    /**
+     * Map key: datagramId, value: SendSatelliteDatagramArgument to retry sending non-emergency
+     * datagrams.
+     */
+    @GuardedBy("mLock")
+    private final LinkedHashMap<Long, SendSatelliteDatagramArgument>
+            mPendingNonEmergencyDatagramsMap = new LinkedHashMap<>();
+
+    /**
+     * Create the DatagramDispatcher singleton instance.
+     * @param context The Context to use to create the DatagramDispatcher.
+     * @param looper The looper for the handler.
+     * @param datagramController DatagramController which is used to update datagram transfer state.
+     * @return The singleton instance of DatagramDispatcher.
+     */
+    public static DatagramDispatcher make(@NonNull Context context, @NonNull Looper looper,
+            @NonNull DatagramController datagramController) {
+        if (sInstance == null) {
+            sInstance = new DatagramDispatcher(context, looper, datagramController);
+        }
+        return sInstance;
+    }
+
+    /**
+     * Create a DatagramDispatcher to send satellite datagrams.
+     *
+     * @param context The Context for the DatagramDispatcher.
+     * @param looper The looper for the handler.
+     * @param datagramController DatagramController which is used to update datagram transfer state.
+     */
+    @VisibleForTesting
+    protected DatagramDispatcher(@NonNull Context context, @NonNull Looper looper,
+            @NonNull DatagramController datagramController) {
+        super(looper);
+        mContext = context;
+        mDatagramController = datagramController;
+        mControllerMetricsStats = ControllerMetricsStats.getInstance();
+
+        synchronized (mLock) {
+            mSendingDatagramInProgress = false;
+        }
+    }
+
+    private static final class DatagramDispatcherHandlerRequest {
+        /** The argument to use for the request */
+        public @NonNull Object argument;
+        /** The caller needs to specify the phone to be used for the request */
+        public @NonNull Phone phone;
+        /** The result of the request that is run on the main thread */
+        public @Nullable Object result;
+
+        DatagramDispatcherHandlerRequest(Object argument, Phone phone) {
+            this.argument = argument;
+            this.phone = phone;
+        }
+    }
+
+    private static final class SendSatelliteDatagramArgument {
+        public int subId;
+        public long datagramId;
+        public @SatelliteManager.DatagramType int datagramType;
+        public @NonNull SatelliteDatagram datagram;
+        public boolean needFullScreenPointingUI;
+        public @NonNull Consumer<Integer> callback;
+        public long datagramStartTime;
+        public boolean skipCheckingSatelliteAligned = false;
+
+        SendSatelliteDatagramArgument(int subId, long datagramId,
+                @SatelliteManager.DatagramType int datagramType,
+                @NonNull SatelliteDatagram datagram, boolean needFullScreenPointingUI,
+                @NonNull Consumer<Integer> callback) {
+            this.subId = subId;
+            this.datagramId = datagramId;
+            this.datagramType = datagramType;
+            this.datagram = datagram;
+            this.needFullScreenPointingUI = needFullScreenPointingUI;
+            this.callback = callback;
+        }
+
+        /** returns the size of outgoing SMS, rounded by 10 bytes */
+        public int getDatagramRoundedSizeBytes() {
+            if (datagram.getSatelliteDatagram() != null) {
+                int sizeBytes = datagram.getSatelliteDatagram().length;
+                // rounded by ROUNDING_UNIT
+                return (int) (Math.round((double) sizeBytes / ROUNDING_UNIT) * ROUNDING_UNIT);
+            } else {
+                return 0;
+            }
+        }
+
+        /** sets the start time at datagram is sent out */
+        public void setDatagramStartTime() {
+            datagramStartTime =
+                    datagramStartTime == 0 ? System.currentTimeMillis() : datagramStartTime;
+        }
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        DatagramDispatcherHandlerRequest request;
+        Message onCompleted;
+        AsyncResult ar;
+
+        switch(msg.what) {
+            case CMD_SEND_SATELLITE_DATAGRAM: {
+                logd("CMD_SEND_SATELLITE_DATAGRAM");
+                request = (DatagramDispatcherHandlerRequest) msg.obj;
+                SendSatelliteDatagramArgument argument =
+                        (SendSatelliteDatagramArgument) request.argument;
+                onCompleted = obtainMessage(EVENT_SEND_SATELLITE_DATAGRAM_DONE, request);
+
+                if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) {
+                    SatelliteModemInterface.getInstance().sendSatelliteDatagram(argument.datagram,
+                            argument.datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+                            argument.needFullScreenPointingUI, onCompleted);
+                    break;
+                }
+
+                Phone phone = request.phone;
+                if (phone != null) {
+                    phone.sendSatelliteDatagram(onCompleted, argument.datagram,
+                            argument.needFullScreenPointingUI);
+                } else {
+                    loge("sendSatelliteDatagram: No phone object");
+                    synchronized (mLock) {
+                        // Remove current datagram from pending map
+                        if (argument.datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) {
+                            mPendingEmergencyDatagramsMap.remove(argument.datagramId);
+                        } else {
+                            mPendingNonEmergencyDatagramsMap.remove(argument.datagramId);
+                        }
+
+                        // Update send status
+                        mDatagramController.updateSendStatus(argument.subId,
+                                SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED,
+                                getPendingDatagramCount(),
+                                SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+                        mDatagramController.updateSendStatus(argument.subId,
+                                SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                                0, SatelliteManager.SATELLITE_ERROR_NONE);
+
+                        // report phone == null case
+                        reportSendDatagramCompleted(argument,
+                                SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+                        argument.callback.accept(
+                                SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+
+                        // Abort sending all the pending datagrams
+                        abortSendingPendingDatagrams(argument.subId,
+                                SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+                    }
+                }
+                break;
+            }
+
+            case EVENT_SEND_SATELLITE_DATAGRAM_DONE: {
+                ar = (AsyncResult) msg.obj;
+                request = (DatagramDispatcherHandlerRequest) ar.userObj;
+                int error = SatelliteServiceUtils.getSatelliteError(ar, "sendSatelliteDatagram");
+                SendSatelliteDatagramArgument argument =
+                        (SendSatelliteDatagramArgument) request.argument;
+
+                synchronized (mLock) {
+                    if (mIsDemoMode && (error == SatelliteManager.SATELLITE_ERROR_NONE)) {
+                        if (argument.skipCheckingSatelliteAligned) {
+                            logd("Satellite was already aligned. No need to check alignment again");
+                        } else if (!mIsAligned) {
+                            logd("Satellite is not aligned in demo mode, wait for the alignment.");
+                            startSatelliteAlignedTimer(request);
+                            break;
+                        }
+                    }
+
+                    logd("EVENT_SEND_SATELLITE_DATAGRAM_DONE error: " + error);
+                    // log metrics about the outgoing datagram
+                    reportSendDatagramCompleted(argument, error);
+
+                    mSendingDatagramInProgress = false;
+
+                    // Remove current datagram from pending map.
+                    if (argument.datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) {
+                        mPendingEmergencyDatagramsMap.remove(argument.datagramId);
+                    } else {
+                        mPendingNonEmergencyDatagramsMap.remove(argument.datagramId);
+                    }
+
+                    if (error == SatelliteManager.SATELLITE_ERROR_NONE) {
+                        // Update send status for current datagram
+                        mDatagramController.updateSendStatus(argument.subId,
+                                SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS,
+                                getPendingDatagramCount(), error);
+                        mControllerMetricsStats.reportOutgoingDatagramSuccessCount(
+                                argument.datagramType);
+
+                        if (getPendingDatagramCount() > 0) {
+                            // Send response for current datagram
+                            argument.callback.accept(error);
+                            // Send pending datagrams
+                            sendPendingDatagrams();
+                        } else {
+                            mDatagramController.updateSendStatus(argument.subId,
+                                    SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                                    0, SatelliteManager.SATELLITE_ERROR_NONE);
+                            // Send response for current datagram
+                            argument.callback.accept(error);
+                        }
+                    } else {
+                        // Update send status
+                        mDatagramController.updateSendStatus(argument.subId,
+                                SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED,
+                                getPendingDatagramCount(), error);
+                        mDatagramController.updateSendStatus(argument.subId,
+                                SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                                0, SatelliteManager.SATELLITE_ERROR_NONE);
+                        // Send response for current datagram
+                        // after updating datagram transfer state internally.
+                        argument.callback.accept(error);
+                        // Abort sending all the pending datagrams
+                        mControllerMetricsStats.reportOutgoingDatagramFailCount(
+                                argument.datagramType);
+                        abortSendingPendingDatagrams(argument.subId,
+                                SatelliteManager.SATELLITE_REQUEST_ABORTED);
+                    }
+                }
+                break;
+            }
+
+            case EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT: {
+                handleEventSatelliteAlignedTimeout((DatagramDispatcherHandlerRequest) msg.obj);
+                break;
+            }
+
+            default:
+                logw("DatagramDispatcherHandler: unexpected message code: " + msg.what);
+                break;
+        }
+    }
+
+    /**
+     * Send datagram over satellite.
+     *
+     * Gateway encodes SOS message or location sharing message into a datagram and passes it as
+     * input to this method. Datagram received here will be passed down to modem without any
+     * encoding or encryption.
+     *
+     * @param subId The subId of the subscription to send satellite datagrams for.
+     * @param datagramType datagram type indicating whether the datagram is of type
+     *                     SOS_SMS or LOCATION_SHARING.
+     * @param datagram encoded gateway datagram which is encrypted by the caller.
+     *                 Datagram will be passed down to modem without any encoding or encryption.
+     * @param needFullScreenPointingUI this is used to indicate pointingUI app to open in
+     *                                 full screen mode.
+     * @param callback The callback to get {@link SatelliteManager.SatelliteError} of the request.
+     */
+    public void sendSatelliteDatagram(int subId, @SatelliteManager.DatagramType int datagramType,
+            @NonNull SatelliteDatagram datagram, boolean needFullScreenPointingUI,
+            @NonNull Consumer<Integer> callback) {
+        Phone phone = SatelliteServiceUtils.getPhone();
+
+        long datagramId = mNextDatagramId.getAndUpdate(
+                n -> ((n + 1) % DatagramController.MAX_DATAGRAM_ID));
+
+        SendSatelliteDatagramArgument datagramArgs =
+                new SendSatelliteDatagramArgument(subId, datagramId, datagramType, datagram,
+                        needFullScreenPointingUI, callback);
+
+        synchronized (mLock) {
+            // Add datagram to pending datagram map
+            if (datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) {
+                mPendingEmergencyDatagramsMap.put(datagramId, datagramArgs);
+            } else {
+                mPendingNonEmergencyDatagramsMap.put(datagramId, datagramArgs);
+            }
+
+            // Modem can be busy receiving datagrams, so send datagram only when modem is not busy.
+            if (!mSendingDatagramInProgress && mDatagramController.isPollingInIdleState()) {
+                mSendingDatagramInProgress = true;
+                datagramArgs.setDatagramStartTime();
+                mDatagramController.updateSendStatus(subId,
+                        SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
+                        getPendingDatagramCount(), SatelliteManager.SATELLITE_ERROR_NONE);
+                sendRequestAsync(CMD_SEND_SATELLITE_DATAGRAM, datagramArgs, phone);
+            }
+        }
+    }
+
+    public void retrySendingDatagrams() {
+        synchronized (mLock) {
+            sendPendingDatagrams();
+        }
+    }
+
+    /** Set demo mode
+     *
+     * @param isDemoMode {@code true} means demo mode is on, {@code false} otherwise.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    protected void setDemoMode(boolean isDemoMode) {
+        mIsDemoMode = isDemoMode;
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    protected void onDeviceAlignedWithSatellite(boolean isAligned) {
+        if (mIsDemoMode) {
+            synchronized (mLock) {
+                mIsAligned = isAligned;
+                if (isAligned) handleEventSatelliteAligned();
+            }
+        }
+    }
+
+    private void startSatelliteAlignedTimer(@NonNull DatagramDispatcherHandlerRequest request) {
+        if (isSatelliteAlignedTimerStarted()) {
+            logd("Satellite aligned timer was already started");
+            return;
+        }
+        mSendSatelliteDatagramRequest = request;
+        sendMessageDelayed(
+                obtainMessage(EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT, request),
+                getSatelliteAlignedTimeoutDuration());
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected long getSatelliteAlignedTimeoutDuration() {
+        return mDatagramController.getSatelliteAlignedTimeoutDuration();
+    }
+
+    private void handleEventSatelliteAligned() {
+        if (isSatelliteAlignedTimerStarted()) {
+            stopSatelliteAlignedTimer();
+
+            if (mSendSatelliteDatagramRequest == null) {
+                loge("handleEventSatelliteAligned: mSendSatelliteDatagramRequest is null");
+            } else {
+                SendSatelliteDatagramArgument argument =
+                        (SendSatelliteDatagramArgument) mSendSatelliteDatagramRequest.argument;
+                argument.skipCheckingSatelliteAligned = true;
+                Message message = obtainMessage(
+                        EVENT_SEND_SATELLITE_DATAGRAM_DONE, mSendSatelliteDatagramRequest);
+                mSendSatelliteDatagramRequest = null;
+                AsyncResult.forMessage(message, null, null);
+                message.sendToTarget();
+            }
+        }
+    }
+
+    private void handleEventSatelliteAlignedTimeout(
+            @NonNull DatagramDispatcherHandlerRequest request) {
+        SatelliteManager.SatelliteException exception =
+                new SatelliteManager.SatelliteException(
+                        SatelliteManager.SATELLITE_NOT_REACHABLE);
+        Message message = obtainMessage(EVENT_SEND_SATELLITE_DATAGRAM_DONE, request);
+        AsyncResult.forMessage(message, null, exception);
+        message.sendToTarget();
+    }
+
+    private boolean isSatelliteAlignedTimerStarted() {
+        return hasMessages(EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT);
+    }
+
+    private void stopSatelliteAlignedTimer() {
+        removeMessages(EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT);
+    }
+
+    /**
+     * Send pending satellite datagrams. Emergency datagrams are given priority over
+     * non-emergency datagrams.
+     */
+    @GuardedBy("mLock")
+    private void sendPendingDatagrams() {
+        logd("sendPendingDatagrams()");
+        if (!mDatagramController.isPollingInIdleState()) {
+            // Datagram should be sent to satellite modem when modem is free.
+            logd("sendPendingDatagrams: modem is receiving datagrams");
+            return;
+        }
+
+        if (getPendingDatagramCount() <= 0) {
+            logd("sendPendingDatagrams: no pending datagrams to send");
+            return;
+        }
+
+        Phone phone = SatelliteServiceUtils.getPhone();
+        Set<Entry<Long, SendSatelliteDatagramArgument>> pendingDatagram = null;
+        if (!mSendingDatagramInProgress && !mPendingEmergencyDatagramsMap.isEmpty()) {
+            pendingDatagram = mPendingEmergencyDatagramsMap.entrySet();
+        } else if (!mSendingDatagramInProgress && !mPendingNonEmergencyDatagramsMap.isEmpty()) {
+            pendingDatagram = mPendingNonEmergencyDatagramsMap.entrySet();
+        }
+
+        if ((pendingDatagram != null) && pendingDatagram.iterator().hasNext()) {
+            mSendingDatagramInProgress = true;
+            SendSatelliteDatagramArgument datagramArg =
+                    pendingDatagram.iterator().next().getValue();
+            // Sets the trigger time for getting pending datagrams
+            datagramArg.setDatagramStartTime();
+            mDatagramController.updateSendStatus(datagramArg.subId,
+                    SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
+                    getPendingDatagramCount(), SatelliteManager.SATELLITE_ERROR_NONE);
+            sendRequestAsync(CMD_SEND_SATELLITE_DATAGRAM, datagramArg, phone);
+        }
+    }
+
+    /**
+     * Send error code to all the pending datagrams
+     *
+     * @param pendingDatagramsMap The pending datagrams map to be cleaned up.
+     * @param errorCode error code to be returned.
+     */
+    @GuardedBy("mLock")
+    private void sendErrorCodeAndCleanupPendingDatagrams(
+            LinkedHashMap<Long, SendSatelliteDatagramArgument> pendingDatagramsMap,
+            @SatelliteManager.SatelliteError int errorCode) {
+        if (pendingDatagramsMap.size() == 0) {
+            return;
+        }
+        loge("sendErrorCodeAndCleanupPendingDatagrams: cleaning up resources");
+
+        // Send error code to all the pending datagrams
+        for (Entry<Long, SendSatelliteDatagramArgument> entry :
+                pendingDatagramsMap.entrySet()) {
+            SendSatelliteDatagramArgument argument = entry.getValue();
+            reportSendDatagramCompleted(argument, errorCode);
+            mControllerMetricsStats.reportOutgoingDatagramFailCount(argument.datagramType);
+            argument.callback.accept(errorCode);
+        }
+
+        // Clear pending datagram maps
+        pendingDatagramsMap.clear();
+    }
+
+    /**
+     * Abort sending all the pending datagrams.
+     *
+     * @param subId The subId of the subscription used to send datagram
+     * @param errorCode The error code that resulted in abort.
+     */
+    @GuardedBy("mLock")
+    private void abortSendingPendingDatagrams(int subId,
+            @SatelliteManager.SatelliteError int errorCode) {
+        logd("abortSendingPendingDatagrams()");
+        sendErrorCodeAndCleanupPendingDatagrams(mPendingEmergencyDatagramsMap, errorCode);
+        sendErrorCodeAndCleanupPendingDatagrams(mPendingNonEmergencyDatagramsMap, errorCode);
+    }
+
+    /**
+     * Return pending datagram count
+     * @return pending datagram count
+     */
+    @GuardedBy("mLock")
+    private int getPendingDatagramCount() {
+        return mPendingEmergencyDatagramsMap.size() + mPendingNonEmergencyDatagramsMap.size();
+    }
+
+    /**
+     * 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
+     * @param phone phone object used to perform the operation.
+     */
+    private void sendRequestAsync(int command, @NonNull Object argument, @Nullable Phone phone) {
+        DatagramDispatcherHandlerRequest request = new DatagramDispatcherHandlerRequest(
+                argument, phone);
+        Message msg = this.obtainMessage(command, request);
+        msg.sendToTarget();
+    }
+
+    private void reportSendDatagramCompleted(@NonNull SendSatelliteDatagramArgument argument,
+            @NonNull @SatelliteManager.SatelliteError int resultCode) {
+        SatelliteStats.getInstance().onSatelliteOutgoingDatagramMetrics(
+                new SatelliteStats.SatelliteOutgoingDatagramParams.Builder()
+                        .setDatagramType(argument.datagramType)
+                        .setResultCode(resultCode)
+                        .setDatagramSizeBytes(argument.getDatagramRoundedSizeBytes())
+                        .setDatagramTransferTimeMillis(
+                                System.currentTimeMillis() - argument.datagramStartTime)
+                        .build());
+    }
+
+    /**
+     * Destroys this DatagramDispatcher. Used for tearing down static resources during testing.
+     */
+    @VisibleForTesting
+    public void destroy() {
+        sInstance = null;
+    }
+
+    /**
+     * This function is used by {@link DatagramController} to notify {@link DatagramDispatcher}
+     * that satellite modem state has changed.
+     *
+     * @param state Current satellite modem state.
+     */
+    public void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state) {
+        synchronized (mLock) {
+            if (state == SatelliteManager.SATELLITE_MODEM_STATE_OFF
+                    || state == SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE) {
+                logd("onSatelliteModemStateChanged: cleaning up resources");
+                cleanUpResources();
+            } else if (state == SatelliteManager.SATELLITE_MODEM_STATE_IDLE) {
+                sendPendingDatagrams();
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void cleanUpResources() {
+        mSendingDatagramInProgress = false;
+        if (getPendingDatagramCount() > 0) {
+            mDatagramController.updateSendStatus(
+                    SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                    SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED,
+                    getPendingDatagramCount(), SatelliteManager.SATELLITE_REQUEST_ABORTED);
+        }
+        mDatagramController.updateSendStatus(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                0, SatelliteManager.SATELLITE_ERROR_NONE);
+        abortSendingPendingDatagrams(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                SatelliteManager.SATELLITE_REQUEST_ABORTED);
+
+        stopSatelliteAlignedTimer();
+        mIsDemoMode = false;
+        mSendSatelliteDatagramRequest = null;
+        mIsAligned = false;
+    }
+
+    private static void logd(@NonNull String log) {
+        Rlog.d(TAG, log);
+    }
+
+    private static void loge(@NonNull String log) {
+        Rlog.e(TAG, log);
+    }
+
+    private static void logw(@NonNull String log) { Rlog.w(TAG, log); }
+}
diff --git a/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java b/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java
new file mode 100644
index 0000000..06eede1
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java
@@ -0,0 +1,805 @@
+/*
+ * 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.satellite;
+
+import static com.android.internal.telephony.satellite.DatagramController.ROUNDING_UNIT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.net.Uri;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.provider.Telephony;
+import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
+import android.telephony.satellite.ISatelliteDatagramCallback;
+import android.telephony.satellite.SatelliteDatagram;
+import android.telephony.satellite.SatelliteManager;
+import android.util.Pair;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.IIntegerConsumer;
+import com.android.internal.telephony.IVoidConsumer;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.metrics.SatelliteStats;
+import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats;
+import com.android.internal.util.FunctionalUtils;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Consumer;
+
+/**
+ * Datagram receiver used to receive satellite datagrams and then,
+ * deliver received datagrams to messaging apps.
+ */
+public class DatagramReceiver extends Handler {
+    private static final String TAG = "DatagramReceiver";
+
+    private static final int CMD_POLL_PENDING_SATELLITE_DATAGRAMS = 1;
+    private static final int EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE = 2;
+    private static final int EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT = 3;
+
+    /** Key used to read/write satellite datagramId in shared preferences. */
+    private static final String SATELLITE_DATAGRAM_ID_KEY = "satellite_datagram_id_key";
+    private static AtomicLong mNextDatagramId = new AtomicLong(0);
+
+    @NonNull private static DatagramReceiver sInstance;
+    @NonNull private final Context mContext;
+    @NonNull private final ContentResolver mContentResolver;
+    @NonNull private SharedPreferences mSharedPreferences = null;
+    @NonNull private final DatagramController mDatagramController;
+    @NonNull private final ControllerMetricsStats mControllerMetricsStats;
+    @NonNull private final Looper mLooper;
+
+    private long mDatagramTransferStartTime = 0;
+    private boolean mIsDemoMode = false;
+    @GuardedBy("mLock")
+    private boolean mIsAligned = false;
+    private DatagramReceiverHandlerRequest mPollPendingSatelliteDatagramsRequest = null;
+    private final Object mLock = new Object();
+
+    /**
+     * Map key: subId, value: SatelliteDatagramListenerHandler to notify registrants.
+     */
+    private final ConcurrentHashMap<Integer, SatelliteDatagramListenerHandler>
+            mSatelliteDatagramListenerHandlers = new ConcurrentHashMap<>();
+
+    /**
+     * Map key: DatagramId, value: pendingAckCount
+     * This map is used to track number of listeners that are yet to send ack for a particular
+     * datagram.
+     */
+    private final ConcurrentHashMap<Long, Integer>
+            mPendingAckCountHashMap = new ConcurrentHashMap<>();
+
+    /**
+     * Create the DatagramReceiver singleton instance.
+     * @param context The Context to use to create the DatagramReceiver.
+     * @param looper The looper for the handler.
+     * @param datagramController DatagramController which is used to update datagram transfer state.
+     * @return The singleton instance of DatagramReceiver.
+     */
+    public static DatagramReceiver make(@NonNull Context context, @NonNull Looper looper,
+            @NonNull DatagramController datagramController) {
+        if (sInstance == null) {
+            sInstance = new DatagramReceiver(context, looper, datagramController);
+        }
+        return sInstance;
+    }
+
+    /**
+     * Create a DatagramReceiver to received satellite datagrams.
+     * The received datagrams will be delivered to respective messaging apps.
+     *
+     * @param context The Context for the DatagramReceiver.
+     * @param looper The looper for the handler.
+     * @param datagramController DatagramController which is used to update datagram transfer state.
+     */
+    @VisibleForTesting
+    protected DatagramReceiver(@NonNull Context context, @NonNull Looper looper,
+            @NonNull DatagramController datagramController) {
+        super(looper);
+        mContext = context;
+        mLooper = looper;
+        mContentResolver = context.getContentResolver();
+        mDatagramController = datagramController;
+        mControllerMetricsStats = ControllerMetricsStats.getInstance();
+
+        try {
+            mSharedPreferences =
+                    mContext.getSharedPreferences(SatelliteController.SATELLITE_SHARED_PREF,
+                            Context.MODE_PRIVATE);
+        } catch (Exception e) {
+            loge("Cannot get default shared preferences: " + e);
+        }
+
+        if ((mSharedPreferences != null) &&
+                (!mSharedPreferences.contains(SATELLITE_DATAGRAM_ID_KEY))) {
+            mSharedPreferences.edit().putLong(SATELLITE_DATAGRAM_ID_KEY, mNextDatagramId.get())
+                    .commit();
+        }
+    }
+
+    private static final class DatagramReceiverHandlerRequest {
+        /** The argument to use for the request */
+        public @NonNull Object argument;
+        /** The caller needs to specify the phone to be used for the request */
+        public @NonNull Phone phone;
+        /** The subId of the subscription used for the request. */
+        public int subId;
+        /** The result of the request that is run on the main thread */
+        public @Nullable Object result;
+
+        DatagramReceiverHandlerRequest(Object argument, Phone phone, int subId) {
+            this.argument = argument;
+            this.phone = phone;
+            this.subId = subId;
+        }
+    }
+
+    /**
+     * Listeners are updated about incoming datagrams using a backgroundThread.
+     */
+    @VisibleForTesting
+    public static final class SatelliteDatagramListenerHandler extends Handler {
+        public static final int EVENT_SATELLITE_DATAGRAM_RECEIVED = 1;
+        public static final int EVENT_RETRY_DELIVERING_RECEIVED_DATAGRAM = 2;
+        public static final int EVENT_RECEIVED_ACK = 3;
+
+        @NonNull private final ConcurrentHashMap<IBinder, ISatelliteDatagramCallback> mListeners;
+        private final int mSubId;
+
+        private static final class DatagramRetryArgument {
+            public long datagramId;
+            @NonNull public SatelliteDatagram datagram;
+            public int pendingCount;
+            @NonNull public ISatelliteDatagramCallback listener;
+
+            DatagramRetryArgument(long datagramId, @NonNull SatelliteDatagram datagram,
+                    int pendingCount, @NonNull ISatelliteDatagramCallback listener) {
+                this.datagramId = datagramId;
+                this.datagram = datagram;
+                this.pendingCount = pendingCount;
+                this.listener = listener;
+            }
+
+            @Override
+            public boolean equals(Object other) {
+                if (this == other) return true;
+                if (other == null || getClass() != other.getClass()) return false;
+                DatagramRetryArgument that = (DatagramRetryArgument) other;
+                return datagramId == that.datagramId
+                        && datagram.equals(that.datagram)
+                        && pendingCount  == that.pendingCount
+                        && listener.equals(that.listener);
+            }
+        }
+
+        @VisibleForTesting
+        public SatelliteDatagramListenerHandler(@NonNull Looper looper, int subId) {
+            super(looper);
+            mSubId = subId;
+            mListeners = new ConcurrentHashMap<>();
+        }
+
+        public void addListener(@NonNull ISatelliteDatagramCallback listener) {
+            mListeners.put(listener.asBinder(), listener);
+        }
+
+        public void removeListener(@NonNull ISatelliteDatagramCallback listener) {
+            mListeners.remove(listener.asBinder());
+        }
+
+        public boolean hasListeners() {
+            return !mListeners.isEmpty();
+        }
+
+        public int getNumOfListeners() {
+            return mListeners.size();
+        }
+
+        private int getTimeoutToReceiveAck() {
+            return sInstance.mContext.getResources().getInteger(
+                    R.integer.config_timeout_to_receive_delivered_ack_millis);
+        }
+
+        private long getDatagramId() {
+            long datagramId = 0;
+            if (sInstance.mSharedPreferences == null) {
+                try {
+                    // Try to recreate if it is null
+                    sInstance.mSharedPreferences = sInstance.mContext
+                            .getSharedPreferences(SatelliteController.SATELLITE_SHARED_PREF,
+                            Context.MODE_PRIVATE);
+                } catch (Exception e) {
+                    loge("Cannot get default shared preferences: " + e);
+                }
+            }
+
+            if (sInstance.mSharedPreferences != null) {
+                long prevDatagramId = sInstance.mSharedPreferences
+                        .getLong(SATELLITE_DATAGRAM_ID_KEY, mNextDatagramId.get());
+                datagramId = (prevDatagramId + 1) % DatagramController.MAX_DATAGRAM_ID;
+                mNextDatagramId.set(datagramId);
+                sInstance.mSharedPreferences.edit().putLong(SATELLITE_DATAGRAM_ID_KEY, datagramId)
+                        .commit();
+            } else {
+                loge("Shared preferences is null - returning default datagramId");
+                datagramId = mNextDatagramId.getAndUpdate(
+                        n -> ((n + 1) % DatagramController.MAX_DATAGRAM_ID));
+            }
+
+            return datagramId;
+        }
+
+        private void insertDatagram(long datagramId, @NonNull SatelliteDatagram datagram) {
+            ContentValues contentValues = new ContentValues();
+            contentValues.put(
+                    Telephony.SatelliteDatagrams.COLUMN_UNIQUE_KEY_DATAGRAM_ID, datagramId);
+            contentValues.put(
+                    Telephony.SatelliteDatagrams.COLUMN_DATAGRAM, datagram.getSatelliteDatagram());
+            Uri uri = sInstance.mContentResolver.insert(Telephony.SatelliteDatagrams.CONTENT_URI,
+                    contentValues);
+            if (uri == null) {
+                loge("Cannot insert datagram with datagramId: " + datagramId);
+            } else {
+                logd("Inserted datagram with datagramId: " + datagramId);
+            }
+        }
+
+        private void deleteDatagram(long datagramId) {
+            String whereClause = (Telephony.SatelliteDatagrams.COLUMN_UNIQUE_KEY_DATAGRAM_ID
+                    + "=" + datagramId);
+            try (Cursor cursor = sInstance.mContentResolver.query(
+                    Telephony.SatelliteDatagrams.CONTENT_URI,
+                    null, whereClause, null, null)) {
+                if ((cursor != null) && (cursor.getCount() == 1)) {
+                    int numRowsDeleted = sInstance.mContentResolver.delete(
+                            Telephony.SatelliteDatagrams.CONTENT_URI, whereClause, null);
+                    if (numRowsDeleted == 0) {
+                        loge("Cannot delete datagram with datagramId: " + datagramId);
+                    } else {
+                        logd("Deleted datagram with datagramId: " + datagramId);
+                    }
+                } else {
+                    loge("Datagram with datagramId: " + datagramId + " is not present in DB.");
+                }
+            } catch(SQLException e) {
+                loge("deleteDatagram SQLException e:" + e);
+            }
+        }
+
+        private void onSatelliteDatagramReceived(@NonNull DatagramRetryArgument argument) {
+            try {
+                IVoidConsumer internalAck = new IVoidConsumer.Stub() {
+                    /**
+                     * This callback will be used by datagram receiver app
+                     * to send ack back to Telephony. If the callback is not
+                     * received within five minutes, then Telephony will
+                     * resend the datagram again.
+                     */
+                    @Override
+                    public void accept() {
+                        logd("acknowledgeSatelliteDatagramReceived: "
+                                + "datagramId=" + argument.datagramId);
+                        sendMessage(obtainMessage(EVENT_RECEIVED_ACK, argument));
+                    }
+                };
+
+                argument.listener.onSatelliteDatagramReceived(argument.datagramId,
+                        argument.datagram, argument.pendingCount, internalAck);
+            } catch (RemoteException e) {
+                logd("EVENT_SATELLITE_DATAGRAM_RECEIVED RemoteException: " + e);
+            }
+        }
+
+        @Override
+        public void handleMessage(@NonNull Message msg) {
+            switch (msg.what) {
+                case EVENT_SATELLITE_DATAGRAM_RECEIVED: {
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    Pair<SatelliteDatagram, Integer> result =
+                            (Pair<SatelliteDatagram, Integer>) ar.result;
+                    SatelliteDatagram satelliteDatagram = result.first;
+                    int pendingCount = result.second;
+                    logd("Received EVENT_SATELLITE_DATAGRAM_RECEIVED for subId=" + mSubId
+                            + " pendingCount:" + pendingCount);
+
+                    if (pendingCount <= 0 && satelliteDatagram == null) {
+                        sInstance.mDatagramController.updateReceiveStatus(mSubId,
+                                SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE,
+                                pendingCount, SatelliteManager.SATELLITE_ERROR_NONE);
+                    } else if (satelliteDatagram != null) {
+                        sInstance.mDatagramController.updateReceiveStatus(mSubId,
+                                SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS,
+                                pendingCount, SatelliteManager.SATELLITE_ERROR_NONE);
+
+                        long datagramId = getDatagramId();
+                        sInstance.mPendingAckCountHashMap.put(datagramId, getNumOfListeners());
+                        insertDatagram(datagramId, satelliteDatagram);
+
+                        mListeners.values().forEach(listener -> {
+                            DatagramRetryArgument argument = new DatagramRetryArgument(datagramId,
+                                    satelliteDatagram, pendingCount, listener);
+                            onSatelliteDatagramReceived(argument);
+                            // wait for ack and retry after the timeout specified.
+                            sendMessageDelayed(
+                                    obtainMessage(EVENT_RETRY_DELIVERING_RECEIVED_DATAGRAM,
+                                            argument), getTimeoutToReceiveAck());
+                        });
+
+                        sInstance.mControllerMetricsStats.reportIncomingDatagramCount(
+                                SatelliteManager.SATELLITE_ERROR_NONE);
+                    }
+
+                    if (pendingCount <= 0) {
+                        sInstance.mDatagramController.updateReceiveStatus(mSubId,
+                                SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                                pendingCount, SatelliteManager.SATELLITE_ERROR_NONE);
+                    } else {
+                        // Poll for pending datagrams
+                        IIntegerConsumer internalCallback = new IIntegerConsumer.Stub() {
+                            @Override
+                            public void accept(int result) {
+                                logd("pollPendingSatelliteDatagram result: " + result);
+                            }
+                        };
+                        Consumer<Integer> callback = FunctionalUtils.ignoreRemoteException(
+                                internalCallback::accept);
+                        sInstance.pollPendingSatelliteDatagramsInternal(mSubId, callback);
+                    }
+
+                    // Send the captured data about incoming datagram to metric
+                    sInstance.reportMetrics(
+                            satelliteDatagram, SatelliteManager.SATELLITE_ERROR_NONE);
+                    break;
+                }
+
+                case EVENT_RETRY_DELIVERING_RECEIVED_DATAGRAM: {
+                    DatagramRetryArgument argument = (DatagramRetryArgument) msg.obj;
+                    logd("Received EVENT_RETRY_DELIVERING_RECEIVED_DATAGRAM datagramId:"
+                            + argument.datagramId);
+                    onSatelliteDatagramReceived(argument);
+                    break;
+                }
+
+                case EVENT_RECEIVED_ACK: {
+                    DatagramRetryArgument argument = (DatagramRetryArgument) msg.obj;
+                    int pendingAckCount = sInstance.mPendingAckCountHashMap
+                            .get(argument.datagramId);
+                    pendingAckCount -= 1;
+                    sInstance.mPendingAckCountHashMap.put(argument.datagramId, pendingAckCount);
+                    logd("Received EVENT_RECEIVED_ACK datagramId:" + argument.datagramId);
+                    removeMessages(EVENT_RETRY_DELIVERING_RECEIVED_DATAGRAM, argument);
+
+                    if (pendingAckCount <= 0) {
+                        // Delete datagram from DB after receiving ack from all listeners
+                        deleteDatagram(argument.datagramId);
+                        sInstance.mPendingAckCountHashMap.remove(argument.datagramId);
+                    }
+                    break;
+                }
+
+                default:
+                    loge("SatelliteDatagramListenerHandler unknown event: " + msg.what);
+            }
+        }
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        DatagramReceiverHandlerRequest request;
+        Message onCompleted;
+        AsyncResult ar;
+
+        switch (msg.what) {
+            case CMD_POLL_PENDING_SATELLITE_DATAGRAMS: {
+                request = (DatagramReceiverHandlerRequest) msg.obj;
+                onCompleted =
+                        obtainMessage(EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE, request);
+
+                if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) {
+                    SatelliteModemInterface.getInstance()
+                            .pollPendingSatelliteDatagrams(onCompleted);
+                    break;
+                }
+
+                Phone phone = request.phone;
+                if (phone != null) {
+                    phone.pollPendingSatelliteDatagrams(onCompleted);
+                } else {
+                    loge("pollPendingSatelliteDatagrams: No phone object");
+                    mDatagramController.updateReceiveStatus(request.subId,
+                            SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED,
+                            mDatagramController.getReceivePendingCount(),
+                            SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+
+                    mDatagramController.updateReceiveStatus(request.subId,
+                            SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                            mDatagramController.getReceivePendingCount(),
+                            SatelliteManager.SATELLITE_ERROR_NONE);
+
+                    reportMetrics(null, SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+                    mControllerMetricsStats.reportIncomingDatagramCount(
+                                    SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+                    // Send response for current request
+                    ((Consumer<Integer>) request.argument)
+                            .accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+                }
+                break;
+            }
+
+            case EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE: {
+                ar = (AsyncResult) msg.obj;
+                request = (DatagramReceiverHandlerRequest) ar.userObj;
+                int error = SatelliteServiceUtils.getSatelliteError(ar,
+                        "pollPendingSatelliteDatagrams");
+
+                if (mIsDemoMode && error == SatelliteManager.SATELLITE_ERROR_NONE) {
+                    SatelliteDatagram datagram = mDatagramController.getDemoModeDatagram();
+                    final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(
+                            request.subId, mContext);
+                    SatelliteDatagramListenerHandler listenerHandler =
+                            mSatelliteDatagramListenerHandlers.get(validSubId);
+                    if (listenerHandler != null) {
+                        Pair<SatelliteDatagram, Integer> pair = new Pair<>(datagram, 0);
+                        ar = new AsyncResult(null, pair, null);
+                        Message message = listenerHandler.obtainMessage(
+                                SatelliteDatagramListenerHandler.EVENT_SATELLITE_DATAGRAM_RECEIVED,
+                                ar);
+                        listenerHandler.sendMessage(message);
+                    } else {
+                        error = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE;
+                    }
+                }
+
+                logd("EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE error: " + error);
+                if (error != SatelliteManager.SATELLITE_ERROR_NONE) {
+                    mDatagramController.updateReceiveStatus(request.subId,
+                            SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED,
+                            mDatagramController.getReceivePendingCount(), error);
+
+                    mDatagramController.updateReceiveStatus(request.subId,
+                            SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                            mDatagramController.getReceivePendingCount(),
+                            SatelliteManager.SATELLITE_ERROR_NONE);
+
+                    reportMetrics(null, error);
+                    mControllerMetricsStats.reportIncomingDatagramCount(error);
+                }
+                // Send response for current request
+                ((Consumer<Integer>) request.argument).accept(error);
+                break;
+            }
+
+            case EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT: {
+                handleEventSatelliteAlignedTimeout((DatagramReceiverHandlerRequest) msg.obj);
+                break;
+            }
+        }
+    }
+
+    /**
+     * Register to receive incoming datagrams over satellite.
+     *
+     * @param subId The subId of the subscription to register for incoming satellite datagrams.
+     * @param callback The callback to handle incoming datagrams over satellite.
+     *
+     * @return The {@link SatelliteManager.SatelliteError} result of the operation.
+     */
+    @SatelliteManager.SatelliteError public int registerForSatelliteDatagram(int subId,
+            @NonNull ISatelliteDatagramCallback callback) {
+        if (!SatelliteController.getInstance().isSatelliteSupported()) {
+            return SatelliteManager.SATELLITE_NOT_SUPPORTED;
+        }
+
+        final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext);
+        SatelliteDatagramListenerHandler satelliteDatagramListenerHandler =
+                mSatelliteDatagramListenerHandlers.get(validSubId);
+        if (satelliteDatagramListenerHandler == null) {
+            satelliteDatagramListenerHandler = new SatelliteDatagramListenerHandler(
+                    mLooper, validSubId);
+            if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) {
+                SatelliteModemInterface.getInstance().registerForSatelliteDatagramsReceived(
+                        satelliteDatagramListenerHandler,
+                        SatelliteDatagramListenerHandler.EVENT_SATELLITE_DATAGRAM_RECEIVED, null);
+            } else {
+                Phone phone = SatelliteServiceUtils.getPhone();
+                phone.registerForSatelliteDatagramsReceived(satelliteDatagramListenerHandler,
+                        SatelliteDatagramListenerHandler.EVENT_SATELLITE_DATAGRAM_RECEIVED, null);
+            }
+        }
+
+        satelliteDatagramListenerHandler.addListener(callback);
+        mSatelliteDatagramListenerHandlers.put(validSubId, satelliteDatagramListenerHandler);
+        return SatelliteManager.SATELLITE_ERROR_NONE;
+    }
+
+    /**
+     * Unregister to stop receiving incoming datagrams over satellite.
+     * If callback was not registered before, the request will be ignored.
+     *
+     * @param subId The subId of the subscription to unregister for incoming satellite datagrams.
+     * @param callback The callback that was passed to
+     *                 {@link #registerForSatelliteDatagram(int, ISatelliteDatagramCallback)}.
+     */
+    public void unregisterForSatelliteDatagram(int subId,
+            @NonNull ISatelliteDatagramCallback callback) {
+        final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext);
+        SatelliteDatagramListenerHandler handler =
+                mSatelliteDatagramListenerHandlers.get(validSubId);
+        if (handler != null) {
+            handler.removeListener(callback);
+
+            if (!handler.hasListeners()) {
+                mSatelliteDatagramListenerHandlers.remove(validSubId);
+                if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) {
+                    SatelliteModemInterface.getInstance()
+                            .unregisterForSatelliteDatagramsReceived(handler);
+                } else {
+                    Phone phone = SatelliteServiceUtils.getPhone();
+                    if (phone != null) {
+                        phone.unregisterForSatelliteDatagramsReceived(handler);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Poll pending satellite datagrams over satellite.
+     *
+     * This method requests modem to check if there are any pending datagrams to be received over
+     * satellite. If there are any incoming datagrams, they will be received via
+     * {@link android.telephony.satellite.SatelliteDatagramCallback
+     * #onSatelliteDatagramReceived(long, SatelliteDatagram, int, Consumer)}
+     *
+     * @param subId The subId of the subscription used for receiving datagrams.
+     * @param callback The callback to get {@link SatelliteManager.SatelliteError} of the request.
+     */
+    public void pollPendingSatelliteDatagrams(int subId, @NonNull Consumer<Integer> callback) {
+        if (!mDatagramController.isPollingInIdleState()) {
+            // Poll request should be sent to satellite modem only when it is free.
+            logd("pollPendingSatelliteDatagrams: satellite modem is busy receiving datagrams.");
+            callback.accept(SatelliteManager.SATELLITE_MODEM_BUSY);
+            return;
+        }
+
+        pollPendingSatelliteDatagramsInternal(subId, callback);
+    }
+
+    private void pollPendingSatelliteDatagramsInternal(int subId,
+            @NonNull Consumer<Integer> callback) {
+        if (!mDatagramController.isSendingInIdleState()) {
+            // Poll request should be sent to satellite modem only when it is free.
+            logd("pollPendingSatelliteDatagrams: satellite modem is busy sending datagrams.");
+            callback.accept(SatelliteManager.SATELLITE_MODEM_BUSY);
+            return;
+        }
+
+        mDatagramController.updateReceiveStatus(subId,
+                SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING,
+                mDatagramController.getReceivePendingCount(),
+                SatelliteManager.SATELLITE_ERROR_NONE);
+        mDatagramTransferStartTime = System.currentTimeMillis();
+        Phone phone = SatelliteServiceUtils.getPhone();
+
+        if (mIsDemoMode) {
+            DatagramReceiverHandlerRequest request = new DatagramReceiverHandlerRequest(
+                    callback, phone, subId);
+            synchronized (mLock) {
+                if (mIsAligned) {
+                    Message msg = obtainMessage(EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE,
+                            request);
+                    AsyncResult.forMessage(msg, null, null);
+                    msg.sendToTarget();
+                } else {
+                    startSatelliteAlignedTimer(request);
+                }
+            }
+        } else {
+            sendRequestAsync(CMD_POLL_PENDING_SATELLITE_DATAGRAMS, callback, phone, subId);
+        }
+    }
+
+    /**
+     * This function is used by {@link DatagramController} to notify {@link DatagramReceiver}
+     * that satellite modem state has changed.
+     *
+     * @param state Current satellite modem state.
+     */
+    public void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state) {
+        synchronized (mLock) {
+            if (state == SatelliteManager.SATELLITE_MODEM_STATE_OFF
+                    || state == SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE) {
+                logd("onSatelliteModemStateChanged: cleaning up resources");
+                cleanUpResources();
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void cleanupDemoModeResources() {
+        if (isSatelliteAlignedTimerStarted()) {
+            stopSatelliteAlignedTimer();
+            if (mPollPendingSatelliteDatagramsRequest == null) {
+                loge("Satellite aligned timer was started "
+                        + "but mPollPendingSatelliteDatagramsRequest is null");
+            } else {
+                Consumer<Integer> callback =
+                        (Consumer<Integer>) mPollPendingSatelliteDatagramsRequest.argument;
+                callback.accept(SatelliteManager.SATELLITE_REQUEST_ABORTED);
+            }
+        }
+        mIsDemoMode = false;
+        mPollPendingSatelliteDatagramsRequest = null;
+        mIsAligned = false;
+    }
+
+    @GuardedBy("mLock")
+    private void cleanUpResources() {
+        if (mDatagramController.isReceivingDatagrams()) {
+            mDatagramController.updateReceiveStatus(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                    SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED,
+                    mDatagramController.getReceivePendingCount(),
+                    SatelliteManager.SATELLITE_REQUEST_ABORTED);
+        }
+        mDatagramController.updateReceiveStatus(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, 0,
+                SatelliteManager.SATELLITE_ERROR_NONE);
+        cleanupDemoModeResources();
+    }
+
+    /**
+     * 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
+     * @param phone phone object used to perform the operation
+     * @param subId The subId of the subscription used for the request.
+     */
+    private void sendRequestAsync(int command, @NonNull Object argument, @Nullable Phone phone,
+            int subId) {
+        DatagramReceiverHandlerRequest request = new DatagramReceiverHandlerRequest(
+                argument, phone, subId);
+        Message msg = this.obtainMessage(command, request);
+        msg.sendToTarget();
+    }
+
+    /** Report incoming datagram related metrics */
+    private void reportMetrics(@Nullable SatelliteDatagram satelliteDatagram,
+            @NonNull @SatelliteManager.SatelliteError int resultCode) {
+        int datagramSizeRoundedBytes = -1;
+        int datagramTransferTime = 0;
+
+        if (satelliteDatagram != null) {
+            if (satelliteDatagram.getSatelliteDatagram() != null) {
+                int sizeBytes = satelliteDatagram.getSatelliteDatagram().length;
+                // rounded by 10 bytes
+                datagramSizeRoundedBytes =
+                        (int) (Math.round((double) sizeBytes / ROUNDING_UNIT) * ROUNDING_UNIT);
+            }
+            datagramTransferTime = (int) (System.currentTimeMillis() - mDatagramTransferStartTime);
+            mDatagramTransferStartTime = 0;
+        }
+
+        SatelliteStats.getInstance().onSatelliteIncomingDatagramMetrics(
+                new SatelliteStats.SatelliteIncomingDatagramParams.Builder()
+                        .setResultCode(resultCode)
+                        .setDatagramSizeBytes(datagramSizeRoundedBytes)
+                        .setDatagramTransferTimeMillis(datagramTransferTime)
+                        .build());
+    }
+
+    /** Set demo mode
+     *
+     * @param isDemoMode {@code true} means demo mode is on, {@code false} otherwise.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    protected void setDemoMode(boolean isDemoMode) {
+        mIsDemoMode = isDemoMode;
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    protected void onDeviceAlignedWithSatellite(boolean isAligned) {
+        if (mIsDemoMode) {
+            synchronized (mLock) {
+                mIsAligned = isAligned;
+                if (isAligned) handleEventSatelliteAligned();
+            }
+        }
+    }
+
+    private void startSatelliteAlignedTimer(DatagramReceiverHandlerRequest request) {
+        if (isSatelliteAlignedTimerStarted()) {
+            logd("Satellite aligned timer was already started");
+            return;
+        }
+        mPollPendingSatelliteDatagramsRequest = request;
+        sendMessageDelayed(
+                obtainMessage(EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT, request),
+                getSatelliteAlignedTimeoutDuration());
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected long getSatelliteAlignedTimeoutDuration() {
+        return mDatagramController.getSatelliteAlignedTimeoutDuration();
+    }
+
+    private void handleEventSatelliteAligned() {
+        if (isSatelliteAlignedTimerStarted()) {
+            stopSatelliteAlignedTimer();
+
+            if (mPollPendingSatelliteDatagramsRequest == null) {
+                loge("handleSatelliteAlignedTimer: mPollPendingSatelliteDatagramsRequest is null");
+            } else {
+                Message message = obtainMessage(
+                        EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE,
+                        mPollPendingSatelliteDatagramsRequest);
+                mPollPendingSatelliteDatagramsRequest = null;
+                AsyncResult.forMessage(message, null, null);
+                message.sendToTarget();
+            }
+        }
+    }
+
+    private void handleEventSatelliteAlignedTimeout(DatagramReceiverHandlerRequest request) {
+        SatelliteManager.SatelliteException exception =
+                new SatelliteManager.SatelliteException(
+                        SatelliteManager.SATELLITE_NOT_REACHABLE);
+        Message message = obtainMessage(EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE, request);
+        AsyncResult.forMessage(message, null, exception);
+        message.sendToTarget();
+    }
+
+    private boolean isSatelliteAlignedTimerStarted() {
+        return hasMessages(EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT);
+    }
+
+    private void stopSatelliteAlignedTimer() {
+        removeMessages(EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT);
+    }
+
+    /**
+     * Destroys this DatagramDispatcher. Used for tearing down static resources during testing.
+     */
+    @VisibleForTesting
+    public void destroy() {
+        sInstance = null;
+    }
+
+    private static void logd(@NonNull String log) {
+        Rlog.d(TAG, log);
+    }
+
+    private static void loge(@NonNull String log) {
+        Rlog.e(TAG, log);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/satellite/PointingAppController.java b/src/java/com/android/internal/telephony/satellite/PointingAppController.java
new file mode 100644
index 0000000..f7f93cf
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/PointingAppController.java
@@ -0,0 +1,476 @@
+/*
+ * 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.satellite;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncResult;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.telephony.Rlog;
+import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
+import android.telephony.satellite.PointingInfo;
+import android.telephony.satellite.SatelliteManager;
+import android.text.TextUtils;
+
+import com.android.internal.R;
+import com.android.internal.telephony.Phone;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+
+/**
+ * PointingApp controller to manage interactions with PointingUI app.
+ */
+public class PointingAppController {
+    private static final String TAG = "PointingAppController";
+    private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem";
+    private static final boolean DEBUG = !"user".equals(Build.TYPE);
+
+    @NonNull
+    private static PointingAppController sInstance;
+    @NonNull private final Context mContext;
+    private boolean mStartedSatelliteTransmissionUpdates;
+    @NonNull private String mPointingUiPackageName = "";
+    @NonNull private String mPointingUiClassName = "";
+
+    /**
+     * Map key: subId, value: SatelliteTransmissionUpdateHandler to notify registrants.
+     */
+    private final ConcurrentHashMap<Integer, SatelliteTransmissionUpdateHandler>
+            mSatelliteTransmissionUpdateHandlers = new ConcurrentHashMap<>();
+
+    /**
+     * @return The singleton instance of PointingAppController.
+     */
+    public static PointingAppController getInstance() {
+        if (sInstance == null) {
+            loge("PointingAppController was not yet initialized.");
+        }
+        return sInstance;
+    }
+
+    /**
+     * Create the PointingAppController singleton instance.
+     * @param context The Context to use to create the PointingAppController.
+     * @return The singleton instance of PointingAppController.
+     */
+    public static PointingAppController make(@NonNull Context context) {
+        if (sInstance == null) {
+            sInstance = new PointingAppController(context);
+        }
+        return sInstance;
+    }
+
+    /**
+     * Create a PointingAppController to manage interactions with PointingUI app.
+     *
+     * @param context The Context for the PointingUIController.
+     */
+    private PointingAppController(@NonNull Context context) {
+        mContext = context;
+        mStartedSatelliteTransmissionUpdates = false;
+    }
+
+    /**
+     * Set the flag mStartedSatelliteTransmissionUpdates to true or false based on the state of
+     * transmission updates
+     * @param startedSatelliteTransmissionUpdates boolean to set the flag
+     */
+    public void setStartedSatelliteTransmissionUpdates(
+            boolean startedSatelliteTransmissionUpdates) {
+        mStartedSatelliteTransmissionUpdates = startedSatelliteTransmissionUpdates;
+    }
+
+    private static final class DatagramTransferStateHandlerRequest {
+        public int datagramTransferState;
+        public int pendingCount;
+        public int errorCode;
+
+        DatagramTransferStateHandlerRequest(int datagramTransferState, int pendingCount,
+                int errorCode) {
+            this.datagramTransferState = datagramTransferState;
+            this.pendingCount = pendingCount;
+            this.errorCode = errorCode;
+        }
+    }
+
+
+    private static final class SatelliteTransmissionUpdateHandler extends Handler {
+        public static final int EVENT_POSITION_INFO_CHANGED = 1;
+        public static final int EVENT_SEND_DATAGRAM_STATE_CHANGED = 2;
+        public static final int EVENT_RECEIVE_DATAGRAM_STATE_CHANGED = 3;
+        public static final int EVENT_DATAGRAM_TRANSFER_STATE_CHANGED = 4;
+
+        private final ConcurrentHashMap<IBinder, ISatelliteTransmissionUpdateCallback> mListeners;
+        SatelliteTransmissionUpdateHandler(Looper looper) {
+            super(looper);
+            mListeners = new ConcurrentHashMap<>();
+        }
+
+        public void addListener(ISatelliteTransmissionUpdateCallback listener) {
+            mListeners.put(listener.asBinder(), listener);
+        }
+
+        public void removeListener(ISatelliteTransmissionUpdateCallback listener) {
+            mListeners.remove(listener.asBinder());
+        }
+
+        public boolean hasListeners() {
+            return !mListeners.isEmpty();
+        }
+
+        @Override
+        public void handleMessage(@NonNull Message msg) {
+            switch (msg.what) {
+                case EVENT_POSITION_INFO_CHANGED: {
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    PointingInfo pointingInfo = (PointingInfo) ar.result;
+                    List<IBinder> toBeRemoved = new ArrayList<>();
+                    mListeners.values().forEach(listener -> {
+                        try {
+                            listener.onSatellitePositionChanged(pointingInfo);
+                        } catch (RemoteException e) {
+                            logd("EVENT_POSITION_INFO_CHANGED RemoteException: " + e);
+                            toBeRemoved.add(listener.asBinder());
+                        }
+                    });
+                    toBeRemoved.forEach(listener -> {
+                        mListeners.remove(listener);
+                    });
+                    break;
+                }
+
+                case EVENT_DATAGRAM_TRANSFER_STATE_CHANGED: {
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    logd("Receive EVENT_DATAGRAM_TRANSFER_STATE_CHANGED state=" + (int) ar.result);
+                    break;
+                }
+
+                case EVENT_SEND_DATAGRAM_STATE_CHANGED: {
+                    logd("Received EVENT_SEND_DATAGRAM_STATE_CHANGED");
+                    DatagramTransferStateHandlerRequest request =
+                            (DatagramTransferStateHandlerRequest) msg.obj;
+                    List<IBinder> toBeRemoved = new ArrayList<>();
+                    mListeners.values().forEach(listener -> {
+                        try {
+                            listener.onSendDatagramStateChanged(request.datagramTransferState,
+                                    request.pendingCount, request.errorCode);
+                        } catch (RemoteException e) {
+                            logd("EVENT_SEND_DATAGRAM_STATE_CHANGED RemoteException: " + e);
+                            toBeRemoved.add(listener.asBinder());
+                        }
+                    });
+                    toBeRemoved.forEach(listener -> {
+                        mListeners.remove(listener);
+                    });
+                    break;
+                }
+
+                case EVENT_RECEIVE_DATAGRAM_STATE_CHANGED: {
+                    logd("Received EVENT_RECEIVE_DATAGRAM_STATE_CHANGED");
+                    DatagramTransferStateHandlerRequest request =
+                            (DatagramTransferStateHandlerRequest) msg.obj;
+                    List<IBinder> toBeRemoved = new ArrayList<>();
+                    mListeners.values().forEach(listener -> {
+                        try {
+                            listener.onReceiveDatagramStateChanged(request.datagramTransferState,
+                                    request.pendingCount, request.errorCode);
+                        } catch (RemoteException e) {
+                            logd("EVENT_RECEIVE_DATAGRAM_STATE_CHANGED RemoteException: " + e);
+                            toBeRemoved.add(listener.asBinder());
+                        }
+                    });
+                    toBeRemoved.forEach(listener -> {
+                        mListeners.remove(listener);
+                    });
+                    break;
+                }
+
+                default:
+                    loge("SatelliteTransmissionUpdateHandler unknown event: " + msg.what);
+            }
+        }
+    }
+
+    /**
+     * Register to start receiving updates for satellite position and datagram transfer state
+     * @param subId The subId of the subscription to register for receiving the updates.
+     * @param callback The callback to notify of satellite transmission updates.
+     * @param phone The Phone object to unregister for receiving the updates.
+     */
+    public void registerForSatelliteTransmissionUpdates(int subId,
+            ISatelliteTransmissionUpdateCallback callback, Phone phone) {
+        SatelliteTransmissionUpdateHandler handler =
+                mSatelliteTransmissionUpdateHandlers.get(subId);
+        if (handler != null) {
+            handler.addListener(callback);
+            return;
+        } else {
+            handler = new SatelliteTransmissionUpdateHandler(Looper.getMainLooper());
+            handler.addListener(callback);
+            mSatelliteTransmissionUpdateHandlers.put(subId, handler);
+            if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) {
+                SatelliteModemInterface.getInstance().registerForSatellitePositionInfoChanged(
+                        handler, SatelliteTransmissionUpdateHandler.EVENT_POSITION_INFO_CHANGED,
+                        null);
+                SatelliteModemInterface.getInstance().registerForDatagramTransferStateChanged(
+                        handler,
+                        SatelliteTransmissionUpdateHandler.EVENT_DATAGRAM_TRANSFER_STATE_CHANGED,
+                        null);
+            } else {
+                phone.registerForSatellitePositionInfoChanged(handler,
+                        SatelliteTransmissionUpdateHandler.EVENT_POSITION_INFO_CHANGED, null);
+            }
+        }
+    }
+
+    /**
+     * Unregister to stop receiving updates on satellite position and datagram transfer state
+     * If the callback was not registered before, it is ignored
+     * @param subId The subId of the subscription to unregister for receiving the updates.
+     * @param result The callback to get the error code in case of failure
+     * @param callback The callback that was passed to {@link
+     * #registerForSatelliteTransmissionUpdates(int, ISatelliteTransmissionUpdateCallback, Phone)}.
+     * @param phone The Phone object to unregister for receiving the updates
+     */
+    public void unregisterForSatelliteTransmissionUpdates(int subId, Consumer<Integer> result,
+            ISatelliteTransmissionUpdateCallback callback, Phone phone) {
+        SatelliteTransmissionUpdateHandler handler =
+                mSatelliteTransmissionUpdateHandlers.get(subId);
+        if (handler != null) {
+            handler.removeListener(callback);
+
+            if (handler.hasListeners()) {
+                result.accept(SatelliteManager.SATELLITE_ERROR_NONE);
+                return;
+            }
+
+            mSatelliteTransmissionUpdateHandlers.remove(subId);
+            if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) {
+                SatelliteModemInterface.getInstance().unregisterForSatellitePositionInfoChanged(
+                        handler);
+                SatelliteModemInterface.getInstance().unregisterForDatagramTransferStateChanged(
+                        handler);
+            } else {
+                if (phone == null) {
+                    result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+                    return;
+                }
+                phone.unregisterForSatellitePositionInfoChanged(handler);
+            }
+        }
+    }
+
+    /**
+     * Start receiving satellite trasmission updates.
+     * This can be called by the pointing UI when the user starts pointing to the satellite.
+     * Modem should continue to report the pointing input as the device or satellite moves.
+     * The transmission updates will be received via
+     * {@link android.telephony.satellite.SatelliteTransmissionUpdateCallback
+     * #onSatellitePositionChanged(pointingInfo)}.
+     */
+    public void startSatelliteTransmissionUpdates(@NonNull Message message, @Nullable Phone phone) {
+        if (mStartedSatelliteTransmissionUpdates) {
+            logd("startSatelliteTransmissionUpdates: already started");
+            AsyncResult.forMessage(message, null, new SatelliteManager.SatelliteException(
+                    SatelliteManager.SATELLITE_ERROR_NONE));
+            message.sendToTarget();
+            return;
+        }
+        if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) {
+            SatelliteModemInterface.getInstance().startSendingSatellitePointingInfo(message);
+            mStartedSatelliteTransmissionUpdates = true;
+            return;
+        }
+        if (phone != null) {
+            phone.startSatellitePositionUpdates(message);
+            mStartedSatelliteTransmissionUpdates = true;
+        } else {
+            loge("startSatelliteTransmissionUpdates: No phone object");
+            AsyncResult.forMessage(message, null, new SatelliteManager.SatelliteException(
+                    SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE));
+            message.sendToTarget();
+        }
+    }
+
+    /**
+     * Stop receiving satellite transmission updates.
+     * This can be called by the pointing UI when the user stops pointing to the satellite.
+     */
+    public void stopSatelliteTransmissionUpdates(@NonNull Message message, @Nullable Phone phone) {
+        if (SatelliteModemInterface.getInstance().isSatelliteServiceSupported()) {
+            SatelliteModemInterface.getInstance().stopSendingSatellitePointingInfo(message);
+            return;
+        }
+        if (phone != null) {
+            phone.stopSatellitePositionUpdates(message);
+        } else {
+            loge("startSatelliteTransmissionUpdates: No phone object");
+            AsyncResult.forMessage(message, null, new SatelliteManager.SatelliteException(
+                    SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE));
+            message.sendToTarget();
+        }
+    }
+
+    /**
+     * Check if Pointing is needed and Launch Pointing UI
+     * @param needFullScreenPointingUI if pointing UI has to be launchd with Full screen
+     */
+    public void startPointingUI(boolean needFullScreenPointingUI) {
+        String packageName = getPointingUiPackageName();
+        if (TextUtils.isEmpty(packageName)) {
+            logd("startPointingUI: config_pointing_ui_package is not set. Ignore the request");
+            return;
+        }
+
+        Intent launchIntent;
+        String className = getPointingUiClassName();
+        if (!TextUtils.isEmpty(className)) {
+            launchIntent = new Intent()
+                    .setComponent(new ComponentName(packageName, className))
+                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        } else {
+            launchIntent = mContext.getPackageManager().getLaunchIntentForPackage(packageName);
+        }
+        if (launchIntent == null) {
+            loge("startPointingUI: launchIntent is null");
+            return;
+        }
+        launchIntent.putExtra("needFullScreen", needFullScreenPointingUI);
+
+        try {
+            mContext.startActivity(launchIntent);
+        } catch (ActivityNotFoundException ex) {
+            loge("startPointingUI: Pointing UI app activity is not found, ex=" + ex);
+        }
+    }
+
+    public void updateSendDatagramTransferState(int subId,
+            @SatelliteManager.SatelliteDatagramTransferState int datagramTransferState,
+            int sendPendingCount, int errorCode) {
+        DatagramTransferStateHandlerRequest request = new DatagramTransferStateHandlerRequest(
+                datagramTransferState, sendPendingCount, errorCode);
+        SatelliteTransmissionUpdateHandler handler =
+                mSatelliteTransmissionUpdateHandlers.get(subId);
+
+        if (handler != null) {
+            Message msg = handler.obtainMessage(
+                    SatelliteTransmissionUpdateHandler.EVENT_SEND_DATAGRAM_STATE_CHANGED,
+                    request);
+            msg.sendToTarget();
+        } else {
+            loge("SatelliteTransmissionUpdateHandler not found for subId: " + subId);
+        }
+    }
+
+    public void updateReceiveDatagramTransferState(int subId,
+            @SatelliteManager.SatelliteDatagramTransferState int datagramTransferState,
+            int receivePendingCount, int errorCode) {
+        DatagramTransferStateHandlerRequest request = new DatagramTransferStateHandlerRequest(
+                datagramTransferState, receivePendingCount, errorCode);
+        SatelliteTransmissionUpdateHandler handler =
+                mSatelliteTransmissionUpdateHandlers.get(subId);
+
+        if (handler != null) {
+            Message msg = handler.obtainMessage(
+                    SatelliteTransmissionUpdateHandler.EVENT_RECEIVE_DATAGRAM_STATE_CHANGED,
+                    request);
+            msg.sendToTarget();
+        } else {
+            loge(" SatelliteTransmissionUpdateHandler not found for subId: " + subId);
+        }
+    }
+
+    /**
+     * This API can be used by only CTS to update satellite pointing UI app package and class names.
+     *
+     * @param packageName The package name of the satellite pointing UI app.
+     * @param className The class name of the satellite pointing UI app.
+     * @return {@code true} if the satellite pointing UI app package and class is set successfully,
+     * {@code false} otherwise.
+     */
+    boolean setSatellitePointingUiClassName(
+            @Nullable String packageName, @Nullable String className) {
+        if (!isMockModemAllowed()) {
+            loge("setSatellitePointingUiClassName: modifying satellite pointing UI package and "
+                    + "class name is not allowed");
+            return false;
+        }
+
+        logd("setSatellitePointingUiClassName: config_pointing_ui_package is updated, new "
+                + "packageName=" + packageName
+                + ", config_pointing_ui_class new className=" + className);
+
+        if (packageName == null || packageName.equals("null")) {
+            mPointingUiPackageName = "";
+            mPointingUiClassName = "";
+        } else {
+            mPointingUiPackageName = packageName;
+            if (className == null || className.equals("null")) {
+                mPointingUiClassName = "";
+            } else {
+                mPointingUiClassName = className;
+            }
+        }
+
+        return true;
+    }
+
+    @NonNull private String getPointingUiPackageName() {
+        if (!TextUtils.isEmpty(mPointingUiPackageName)) {
+            return mPointingUiPackageName;
+        }
+        return TextUtils.emptyIfNull(mContext.getResources().getString(
+                R.string.config_pointing_ui_package));
+    }
+
+    @NonNull private String getPointingUiClassName() {
+        if (!TextUtils.isEmpty(mPointingUiClassName)) {
+            return mPointingUiClassName;
+        }
+        return TextUtils.emptyIfNull(mContext.getResources().getString(
+                R.string.config_pointing_ui_class));
+    }
+
+    private boolean isMockModemAllowed() {
+        return (DEBUG || SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false));
+    }
+
+    private static void logd(@NonNull String log) {
+        Rlog.d(TAG, log);
+    }
+
+    private static void loge(@NonNull String log) {
+        Rlog.e(TAG, log);
+    }
+    /**
+     * TODO: The following needs to be added in this class:
+     * - check if pointingUI crashes - then restart it
+     */
+}
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteController.java b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
new file mode 100644
index 0000000..5cd8444
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
@@ -0,0 +1,2280 @@
+/*
+ * 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.satellite;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+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.database.ContentObserver;
+import android.net.wifi.WifiManager;
+import android.nfc.NfcAdapter;
+import android.nfc.NfcManager;
+import android.os.AsyncResult;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.provider.Settings;
+import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.satellite.ISatelliteDatagramCallback;
+import android.telephony.satellite.ISatelliteProvisionStateCallback;
+import android.telephony.satellite.ISatelliteStateCallback;
+import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
+import android.telephony.satellite.SatelliteCapabilities;
+import android.telephony.satellite.SatelliteDatagram;
+import android.telephony.satellite.SatelliteManager;
+import android.util.Log;
+import android.uwb.UwbManager;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.IIntegerConsumer;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats;
+import com.android.internal.telephony.satellite.metrics.ProvisionMetricsStats;
+import com.android.internal.telephony.satellite.metrics.SessionMetricsStats;
+import com.android.internal.util.FunctionalUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
+/**
+ * Satellite controller is the backend service of
+ * {@link android.telephony.satellite.SatelliteManager}.
+ */
+public class SatelliteController extends Handler {
+    private static final String TAG = "SatelliteController";
+    /** Whether enabling verbose debugging message or not. */
+    private static final boolean DBG = false;
+    /** File used to store shared preferences related to satellite. */
+    public static final String SATELLITE_SHARED_PREF = "satellite_shared_pref";
+    /** Value to pass for the setting key SATELLITE_MODE_ENABLED, enabled = 1, disabled = 0 */
+    public static final int SATELLITE_MODE_ENABLED_TRUE = 1;
+    public static final int SATELLITE_MODE_ENABLED_FALSE = 0;
+
+    /** Message codes used in handleMessage() */
+    //TODO: Move the Commands and events related to position updates to PointingAppController
+    private static final int CMD_START_SATELLITE_TRANSMISSION_UPDATES = 1;
+    private static final int EVENT_START_SATELLITE_TRANSMISSION_UPDATES_DONE = 2;
+    private static final int CMD_STOP_SATELLITE_TRANSMISSION_UPDATES = 3;
+    private static final int EVENT_STOP_SATELLITE_TRANSMISSION_UPDATES_DONE = 4;
+    private static final int CMD_PROVISION_SATELLITE_SERVICE = 7;
+    private static final int EVENT_PROVISION_SATELLITE_SERVICE_DONE = 8;
+    private static final int CMD_DEPROVISION_SATELLITE_SERVICE = 9;
+    private static final int EVENT_DEPROVISION_SATELLITE_SERVICE_DONE = 10;
+    private static final int CMD_SET_SATELLITE_ENABLED = 11;
+    private static final int EVENT_SET_SATELLITE_ENABLED_DONE = 12;
+    private static final int CMD_IS_SATELLITE_ENABLED = 13;
+    private static final int EVENT_IS_SATELLITE_ENABLED_DONE = 14;
+    private static final int CMD_IS_SATELLITE_SUPPORTED = 15;
+    private static final int EVENT_IS_SATELLITE_SUPPORTED_DONE = 16;
+    private static final int CMD_GET_SATELLITE_CAPABILITIES = 17;
+    private static final int EVENT_GET_SATELLITE_CAPABILITIES_DONE = 18;
+    private static final int CMD_IS_SATELLITE_COMMUNICATION_ALLOWED = 19;
+    private static final int EVENT_IS_SATELLITE_COMMUNICATION_ALLOWED_DONE = 20;
+    private static final int CMD_GET_TIME_SATELLITE_NEXT_VISIBLE = 21;
+    private static final int EVENT_GET_TIME_SATELLITE_NEXT_VISIBLE_DONE = 22;
+    private static final int EVENT_RADIO_STATE_CHANGED = 23;
+    private static final int CMD_IS_SATELLITE_PROVISIONED = 24;
+    private static final int EVENT_IS_SATELLITE_PROVISIONED_DONE = 25;
+    private static final int EVENT_SATELLITE_PROVISION_STATE_CHANGED = 26;
+    private static final int EVENT_PENDING_DATAGRAMS = 27;
+    private static final int EVENT_SATELLITE_MODEM_STATE_CHANGED = 28;
+
+    @NonNull private static SatelliteController sInstance;
+    @NonNull private final Context mContext;
+    @NonNull private final SatelliteModemInterface mSatelliteModemInterface;
+    @NonNull private SatelliteSessionController mSatelliteSessionController;
+    @NonNull private final PointingAppController mPointingAppController;
+    @NonNull private final DatagramController mDatagramController;
+    @NonNull private final ControllerMetricsStats mControllerMetricsStats;
+    @NonNull private final ProvisionMetricsStats mProvisionMetricsStats;
+    private SharedPreferences mSharedPreferences = null;
+    private final CommandsInterface mCi;
+    private ContentResolver mContentResolver = null;
+
+    private final Object mRadioStateLock = new Object();
+
+    /** Flags to indicate whether the resepective radio is enabled */
+    @GuardedBy("mRadioStateLock")
+    private boolean mBTStateEnabled = false;
+    @GuardedBy("mRadioStateLock")
+    private boolean mNfcStateEnabled = false;
+    @GuardedBy("mRadioStateLock")
+    private boolean mUwbStateEnabled = false;
+    @GuardedBy("mRadioStateLock")
+    private boolean mWifiStateEnabled = false;
+
+    // Flags to indicate that respective radios need to be disabled when satellite is enabled
+    private boolean mDisableBTOnSatelliteEnabled = false;
+    private boolean mDisableNFCOnSatelliteEnabled = false;
+    private boolean mDisableUWBOnSatelliteEnabled = false;
+    private boolean mDisableWifiOnSatelliteEnabled = false;
+
+    private final Object mSatelliteEnabledRequestLock = new Object();
+    @GuardedBy("mSatelliteEnabledRequestLock")
+    private RequestSatelliteEnabledArgument mSatelliteEnabledRequest = null;
+    /** Flag to indicate that satellite is enabled successfully
+     * and waiting for all the radios to be disabled so that success can be sent to callback
+     */
+    @GuardedBy("mSatelliteEnabledRequestLock")
+    private boolean mWaitingForRadioDisabled = false;
+
+    private final AtomicBoolean mRegisteredForProvisionStateChangedWithSatelliteService =
+            new AtomicBoolean(false);
+    private final AtomicBoolean mRegisteredForProvisionStateChangedWithPhone =
+            new AtomicBoolean(false);
+    private final AtomicBoolean mRegisteredForPendingDatagramCountWithSatelliteService =
+            new AtomicBoolean(false);
+    private final AtomicBoolean mRegisteredForPendingDatagramCountWithPhone =
+            new AtomicBoolean(false);
+    private final AtomicBoolean mRegisteredForSatelliteModemStateChangedWithSatelliteService =
+            new AtomicBoolean(false);
+    private final AtomicBoolean mRegisteredForSatelliteModemStateChangedWithPhone =
+            new AtomicBoolean(false);
+    /**
+     * Map key: subId, value: callback to get error code of the provision request.
+     */
+    private final ConcurrentHashMap<Integer, Consumer<Integer>> mSatelliteProvisionCallbacks =
+            new ConcurrentHashMap<>();
+
+    /**
+     * Map key: binder of the callback, value: callback to receive provision state changed events.
+     */
+    private final ConcurrentHashMap<IBinder, ISatelliteProvisionStateCallback>
+            mSatelliteProvisionStateChangedListeners = new ConcurrentHashMap<>();
+    private final Object mIsSatelliteSupportedLock = new Object();
+    @GuardedBy("mIsSatelliteSupportedLock")
+    private Boolean mIsSatelliteSupported = null;
+    private boolean mIsDemoModeEnabled = false;
+    private final Object mIsSatelliteEnabledLock = new Object();
+    @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 mSatelliteCapabilitiesLock = new Object();
+    @GuardedBy("mSatelliteCapabilitiesLock")
+    private SatelliteCapabilities mSatelliteCapabilities;
+    private final Object mNeedsSatellitePointingLock = new Object();
+    @GuardedBy("mNeedsSatellitePointingLock")
+    private boolean mNeedsSatellitePointing = false;
+
+    /**
+     * @return The singleton instance of SatelliteController.
+     */
+    public static SatelliteController getInstance() {
+        if (sInstance == null) {
+            loge("SatelliteController was not yet initialized.");
+        }
+        return sInstance;
+    }
+
+    /**
+     * Create the SatelliteController singleton instance.
+     * @param context The Context to use to create the SatelliteController.
+     */
+    public static void make(@NonNull Context context) {
+        if (sInstance == null) {
+            HandlerThread satelliteThread = new HandlerThread(TAG);
+            satelliteThread.start();
+            sInstance = new SatelliteController(context, satelliteThread.getLooper());
+        }
+    }
+
+    /**
+     * Create a SatelliteController to act as a backend service of
+     * {@link android.telephony.satellite.SatelliteManager}
+     *
+     * @param context The Context for the SatelliteController.
+     * @param looper The looper for the handler. It does not run on main thread.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public SatelliteController(@NonNull Context context, @NonNull Looper looper) {
+        super(looper);
+
+        mContext = context;
+        Phone phone = SatelliteServiceUtils.getPhone();
+        mCi = phone.mCi;
+        // Create the SatelliteModemInterface singleton, which is used to manage connections
+        // to the satellite service and HAL interface.
+        mSatelliteModemInterface = SatelliteModemInterface.make(mContext, this);
+
+        // Create the PointingUIController singleton,
+        // which is used to manage interactions with PointingUI app.
+        mPointingAppController = PointingAppController.make(mContext);
+
+        // Create the SatelliteControllerMetrics to report controller metrics
+        // should be called before making DatagramController
+        mControllerMetricsStats = ControllerMetricsStats.make(mContext);
+        mProvisionMetricsStats = ProvisionMetricsStats.getOrCreateInstance();
+
+        // Create the DatagramController singleton,
+        // which is used to send and receive satellite datagrams.
+        mDatagramController = DatagramController.make(mContext, looper, mPointingAppController);
+
+        requestIsSatelliteSupported(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                new ResultReceiver(this) {
+                    @Override
+                    protected void onReceiveResult(int resultCode, Bundle resultData) {
+                        logd("requestIsSatelliteSupported: resultCode=" + resultCode);
+                    }
+                });
+        mCi.registerForRadioStateChanged(this, EVENT_RADIO_STATE_CHANGED, null);
+        mIsRadioOn = phone.isRadioOn();
+        registerForSatelliteProvisionStateChanged();
+        registerForPendingDatagramCount();
+        registerForSatelliteModemStateChanged();
+        mContentResolver = mContext.getContentResolver();
+
+        try {
+            mSharedPreferences = mContext.getSharedPreferences(SATELLITE_SHARED_PREF,
+                    Context.MODE_PRIVATE);
+        } catch (Exception e) {
+            loge("Cannot get default shared preferences: " + e);
+        }
+
+        initializeSatelliteModeRadios();
+
+        ContentObserver satelliteModeRadiosContentObserver = new ContentObserver(this) {
+            @Override
+            public void onChange(boolean selfChange) {
+                initializeSatelliteModeRadios();
+            }
+        };
+        if (mContentResolver != null) {
+            mContentResolver.registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.SATELLITE_MODE_RADIOS),
+                    false, satelliteModeRadiosContentObserver);
+        }
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected void initializeSatelliteModeRadios() {
+        if (mContentResolver != null) {
+            BTWifiNFCStateReceiver bTWifiNFCSateReceiver = new BTWifiNFCStateReceiver();
+            UwbAdapterStateCallback uwbAdapterStateCallback = new UwbAdapterStateCallback();
+            IntentFilter radioStateIntentFilter = new IntentFilter();
+
+            synchronized (mRadioStateLock) {
+                // Initialize radio states to default value
+                mDisableBTOnSatelliteEnabled = false;
+                mDisableNFCOnSatelliteEnabled = false;
+                mDisableWifiOnSatelliteEnabled = false;
+                mDisableUWBOnSatelliteEnabled = false;
+
+                mBTStateEnabled = false;
+                mNfcStateEnabled = false;
+                mWifiStateEnabled = false;
+                mUwbStateEnabled = false;
+
+                // Read satellite mode radios from settings
+                String satelliteModeRadios = Settings.Global.getString(mContentResolver,
+                        Settings.Global.SATELLITE_MODE_RADIOS);
+                if (satelliteModeRadios == null) {
+                    loge("initializeSatelliteModeRadios: satelliteModeRadios is null");
+                    return;
+                }
+                logd("Radios To be checked when satellite is on: " + satelliteModeRadios);
+
+                if (satelliteModeRadios.contains(Settings.Global.RADIO_BLUETOOTH)) {
+                    BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+                    if (bluetoothAdapter != null) {
+                        mDisableBTOnSatelliteEnabled = true;
+                        mBTStateEnabled = bluetoothAdapter.isEnabled();
+                        radioStateIntentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+                    }
+                }
+
+                if (satelliteModeRadios.contains(Settings.Global.RADIO_NFC)) {
+                    Context applicationContext = mContext.getApplicationContext();
+                    NfcAdapter nfcAdapter = null;
+                    if (applicationContext != null) {
+                        nfcAdapter = NfcAdapter.getDefaultAdapter(mContext.getApplicationContext());
+                    }
+                    if (nfcAdapter != null) {
+                        mDisableNFCOnSatelliteEnabled = true;
+                        mNfcStateEnabled = nfcAdapter.isEnabled();
+                        radioStateIntentFilter.addAction(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED);
+                    }
+                }
+
+                if (satelliteModeRadios.contains(Settings.Global.RADIO_WIFI)) {
+                    WifiManager wifiManager = mContext.getSystemService(WifiManager.class);
+                    if (wifiManager != null) {
+                        mDisableWifiOnSatelliteEnabled = true;
+                        mWifiStateEnabled = wifiManager.isWifiEnabled();
+                        radioStateIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+                    }
+                }
+                mContext.registerReceiver(bTWifiNFCSateReceiver, radioStateIntentFilter);
+
+                if (satelliteModeRadios.contains(Settings.Global.RADIO_UWB)) {
+                    UwbManager uwbManager = mContext.getSystemService(UwbManager.class);
+                    if (uwbManager != null) {
+                        mDisableUWBOnSatelliteEnabled = true;
+                        mUwbStateEnabled = uwbManager.isUwbEnabled();
+                        final long identity = Binder.clearCallingIdentity();
+                        try {
+                            uwbManager.registerAdapterStateCallback(mContext.getMainExecutor(),
+                                    uwbAdapterStateCallback);
+                        } finally {
+                            Binder.restoreCallingIdentity(identity);
+                        }
+                    }
+                }
+
+                logd("mDisableBTOnSatelliteEnabled: " + mDisableBTOnSatelliteEnabled
+                        + " mDisableNFCOnSatelliteEnabled: " + mDisableNFCOnSatelliteEnabled
+                        + " mDisableWifiOnSatelliteEnabled: " + mDisableWifiOnSatelliteEnabled
+                        + " mDisableUWBOnSatelliteEnabled: " + mDisableUWBOnSatelliteEnabled);
+
+                logd("mBTStateEnabled: " + mBTStateEnabled
+                        + " mNfcStateEnabled: " + mNfcStateEnabled
+                        + " mWifiStateEnabled: " + mWifiStateEnabled
+                        + " mUwbStateEnabled: " + mUwbStateEnabled);
+            }
+        }
+    }
+
+    protected class UwbAdapterStateCallback implements UwbManager.AdapterStateCallback {
+
+        public String toString(int state) {
+            switch (state) {
+                case UwbManager.AdapterStateCallback.STATE_DISABLED:
+                    return "Disabled";
+
+                case UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE:
+                    return "Inactive";
+
+                case UwbManager.AdapterStateCallback.STATE_ENABLED_ACTIVE:
+                    return "Active";
+
+                default:
+                    return "";
+            }
+        }
+
+        @Override
+        public void onStateChanged(int state, int reason) {
+            logd("UwbAdapterStateCallback#onStateChanged() called, state = " + toString(state));
+            logd("Adapter state changed reason " + String.valueOf(reason));
+            synchronized (mRadioStateLock) {
+                if (state == UwbManager.AdapterStateCallback.STATE_DISABLED) {
+                    mUwbStateEnabled = false;
+                    evaluateToSendSatelliteEnabledSuccess();
+                } else {
+                    mUwbStateEnabled = true;
+                }
+                logd("mUwbStateEnabled: " + mUwbStateEnabled);
+            }
+        }
+    }
+
+    protected class BTWifiNFCStateReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (action == null) {
+                logd("BTWifiNFCStateReceiver NULL action for intent " + intent);
+                return;
+            }
+
+            switch (action) {
+                case BluetoothAdapter.ACTION_STATE_CHANGED:
+                    int btState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
+                            BluetoothAdapter.ERROR);
+                    logd("Bluetooth state updated to " + btState);
+                    synchronized (mRadioStateLock) {
+                        if (btState == BluetoothAdapter.STATE_OFF) {
+                            mBTStateEnabled = false;
+                            evaluateToSendSatelliteEnabledSuccess();
+                        } else if (btState == BluetoothAdapter.STATE_ON) {
+                            mBTStateEnabled = true;
+                        }
+                        logd("mBTStateEnabled: " + mBTStateEnabled);
+                    }
+                    break;
+
+                case NfcAdapter.ACTION_ADAPTER_STATE_CHANGED:
+                    int nfcState = intent.getIntExtra(NfcAdapter.EXTRA_ADAPTER_STATE, -1);
+                    logd("Nfc state updated to " + nfcState);
+                    synchronized (mRadioStateLock) {
+                        if (nfcState == NfcAdapter.STATE_ON) {
+                            mNfcStateEnabled = true;
+                        } else if (nfcState == NfcAdapter.STATE_OFF) {
+                            mNfcStateEnabled = false;
+                            evaluateToSendSatelliteEnabledSuccess();
+                        }
+                        logd("mNfcStateEnabled: " + mNfcStateEnabled);
+                    }
+                    break;
+
+                case WifiManager.WIFI_STATE_CHANGED_ACTION:
+                    int wifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
+                            WifiManager.WIFI_STATE_UNKNOWN);
+                    logd("Wifi state updated to " + wifiState);
+                    synchronized (mRadioStateLock) {
+                        if (wifiState == WifiManager.WIFI_STATE_ENABLED) {
+                            mWifiStateEnabled = true;
+                        } else if (wifiState == WifiManager.WIFI_STATE_DISABLED) {
+                            mWifiStateEnabled = false;
+                            evaluateToSendSatelliteEnabledSuccess();
+                        }
+                        logd("mWifiStateEnabled: " + mWifiStateEnabled);
+                    }
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    private static final class SatelliteControllerHandlerRequest {
+        /** The argument to use for the request */
+        public @NonNull Object argument;
+        /** The caller needs to specify the phone to be used for the request */
+        public @NonNull Phone phone;
+        /** The result of the request that is run on the main thread */
+        public @Nullable Object result;
+
+        SatelliteControllerHandlerRequest(Object argument, Phone phone) {
+            this.argument = argument;
+            this.phone = phone;
+        }
+    }
+
+    private static final class RequestSatelliteEnabledArgument {
+        public boolean enableSatellite;
+        public boolean enableDemoMode;
+        @NonNull public Consumer<Integer> callback;
+
+        RequestSatelliteEnabledArgument(boolean enableSatellite, boolean enableDemoMode,
+                Consumer<Integer> callback) {
+            this.enableSatellite = enableSatellite;
+            this.enableDemoMode = enableDemoMode;
+            this.callback = callback;
+        }
+    }
+
+    private static final class ProvisionSatelliteServiceArgument {
+        @NonNull public String token;
+        @NonNull public byte[] provisionData;
+        @NonNull public Consumer<Integer> callback;
+        public int subId;
+
+        ProvisionSatelliteServiceArgument(String token, byte[] provisionData,
+                Consumer<Integer> callback, int subId) {
+            this.token = token;
+            this.provisionData = provisionData;
+            this.callback = callback;
+            this.subId = subId;
+        }
+    }
+
+    /**
+     * Arguments to send to SatelliteTransmissionUpdate registrants
+     */
+    public static final class SatelliteTransmissionUpdateArgument {
+        @NonNull public Consumer<Integer> errorCallback;
+        @NonNull public ISatelliteTransmissionUpdateCallback callback;
+        public int subId;
+
+        SatelliteTransmissionUpdateArgument(Consumer<Integer> errorCallback,
+                ISatelliteTransmissionUpdateCallback callback, int subId) {
+            this.errorCallback = errorCallback;
+            this.callback = callback;
+            this.subId = subId;
+        }
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        SatelliteControllerHandlerRequest request;
+        Message onCompleted;
+        AsyncResult ar;
+
+        switch(msg.what) {
+            case CMD_START_SATELLITE_TRANSMISSION_UPDATES: {
+                request = (SatelliteControllerHandlerRequest) msg.obj;
+                onCompleted =
+                        obtainMessage(EVENT_START_SATELLITE_TRANSMISSION_UPDATES_DONE, request);
+                mPointingAppController.startSatelliteTransmissionUpdates(onCompleted,
+                        request.phone);
+                break;
+            }
+
+            case EVENT_START_SATELLITE_TRANSMISSION_UPDATES_DONE: {
+                handleStartSatelliteTransmissionUpdatesDone((AsyncResult) msg.obj);
+                break;
+            }
+
+            case CMD_STOP_SATELLITE_TRANSMISSION_UPDATES: {
+                request = (SatelliteControllerHandlerRequest) msg.obj;
+                onCompleted =
+                        obtainMessage(EVENT_STOP_SATELLITE_TRANSMISSION_UPDATES_DONE, request);
+                mPointingAppController.stopSatelliteTransmissionUpdates(onCompleted, request.phone);
+                break;
+            }
+
+            case EVENT_STOP_SATELLITE_TRANSMISSION_UPDATES_DONE: {
+                ar = (AsyncResult) msg.obj;
+                request = (SatelliteControllerHandlerRequest) ar.userObj;
+                int error =  SatelliteServiceUtils.getSatelliteError(ar,
+                        "stopSatelliteTransmissionUpdates");
+                ((Consumer<Integer>) request.argument).accept(error);
+                break;
+            }
+
+            case CMD_PROVISION_SATELLITE_SERVICE: {
+                request = (SatelliteControllerHandlerRequest) msg.obj;
+                ProvisionSatelliteServiceArgument argument =
+                        (ProvisionSatelliteServiceArgument) request.argument;
+                if (mSatelliteProvisionCallbacks.containsKey(argument.subId)) {
+                    argument.callback.accept(
+                            SatelliteManager.SATELLITE_SERVICE_PROVISION_IN_PROGRESS);
+                    notifyRequester(request);
+                    break;
+                }
+                mSatelliteProvisionCallbacks.put(argument.subId, argument.callback);
+                onCompleted = obtainMessage(EVENT_PROVISION_SATELLITE_SERVICE_DONE, request);
+                // Log the current time for provision triggered
+                mProvisionMetricsStats.setProvisioningStartTime();
+                if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
+                    mSatelliteModemInterface.provisionSatelliteService(argument.token,
+                            argument.provisionData, onCompleted);
+                    break;
+                }
+                Phone phone = request.phone;
+                if (phone != null) {
+                    phone.provisionSatelliteService(onCompleted, argument.token);
+                } else {
+                    loge("provisionSatelliteService: No phone object");
+                    argument.callback.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+                    notifyRequester(request);
+                    mProvisionMetricsStats
+                            .setResultCode(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE)
+                            .reportProvisionMetrics();
+                    mControllerMetricsStats.reportProvisionCount(
+                            SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+                }
+                break;
+            }
+
+            case EVENT_PROVISION_SATELLITE_SERVICE_DONE: {
+                ar = (AsyncResult) msg.obj;
+                request = (SatelliteControllerHandlerRequest) ar.userObj;
+                int errorCode =  SatelliteServiceUtils.getSatelliteError(ar,
+                        "provisionSatelliteService");
+                handleEventProvisionSatelliteServiceDone(
+                        (ProvisionSatelliteServiceArgument) request.argument, errorCode);
+                notifyRequester(request);
+                break;
+            }
+
+            case CMD_DEPROVISION_SATELLITE_SERVICE: {
+                request = (SatelliteControllerHandlerRequest) msg.obj;
+                ProvisionSatelliteServiceArgument argument =
+                        (ProvisionSatelliteServiceArgument) request.argument;
+                onCompleted = obtainMessage(EVENT_DEPROVISION_SATELLITE_SERVICE_DONE, request);
+                if (argument.callback != null) {
+                    mProvisionMetricsStats.setProvisioningStartTime();
+                }
+                if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
+                    mSatelliteModemInterface
+                            .deprovisionSatelliteService(argument.token, onCompleted);
+                    break;
+                }
+                Phone phone = request.phone;
+                if (phone != null) {
+                    phone.deprovisionSatelliteService(onCompleted, argument.token);
+                } else {
+                    loge("deprovisionSatelliteService: No phone object");
+                    if (argument.callback != null) {
+                        argument.callback.accept(
+                                SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+                        mProvisionMetricsStats
+                                .setResultCode(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE)
+                                .reportProvisionMetrics();
+                        mControllerMetricsStats.reportDeprovisionCount(
+                                SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+                    }
+                }
+                break;
+            }
+
+            case EVENT_DEPROVISION_SATELLITE_SERVICE_DONE: {
+                ar = (AsyncResult) msg.obj;
+                request = (SatelliteControllerHandlerRequest) ar.userObj;
+                int errorCode =  SatelliteServiceUtils.getSatelliteError(ar,
+                        "deprovisionSatelliteService");
+                handleEventDeprovisionSatelliteServiceDone(
+                        (ProvisionSatelliteServiceArgument) request.argument, errorCode);
+                break;
+            }
+
+            case CMD_SET_SATELLITE_ENABLED: {
+                request = (SatelliteControllerHandlerRequest) msg.obj;
+                handleSatelliteEnabled(request);
+                break;
+            }
+
+            case EVENT_SET_SATELLITE_ENABLED_DONE: {
+                ar = (AsyncResult) msg.obj;
+                request = (SatelliteControllerHandlerRequest) ar.userObj;
+                RequestSatelliteEnabledArgument argument =
+                        (RequestSatelliteEnabledArgument) request.argument;
+                int error =  SatelliteServiceUtils.getSatelliteError(ar, "setSatelliteEnabled");
+                logd("EVENT_SET_SATELLITE_ENABLED_DONE = " + error);
+
+                if (error == SatelliteManager.SATELLITE_ERROR_NONE) {
+                    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 {
+                        synchronized (mSatelliteEnabledRequestLock) {
+                            if (mSatelliteEnabledRequest != null &&
+                                    mSatelliteEnabledRequest.enableSatellite == true &&
+                                    argument.enableSatellite == false && mWaitingForRadioDisabled) {
+                                // Previous mSatelliteEnabledRequest is successful but waiting for
+                                // all radios to be turned off.
+                                mSatelliteEnabledRequest.callback.accept(
+                                        SatelliteManager.SATELLITE_ERROR_NONE);
+                            }
+                        }
+                        resetSatelliteEnabledRequest();
+
+                        setSettingsKeyForSatelliteMode(SATELLITE_MODE_ENABLED_FALSE);
+                        setDemoModeEnabled(argument.enableDemoMode);
+                        synchronized (mIsSatelliteEnabledLock) {
+                            mIsSatelliteEnabled = argument.enableSatellite;
+                        }
+                        // If satellite is disabled, send success to callback immediately
+                        argument.callback.accept(error);
+                        updateSatelliteEnabledState(
+                                argument.enableSatellite, "EVENT_SET_SATELLITE_ENABLED_DONE");
+                    }
+                } else {
+                    synchronized (mSatelliteEnabledRequestLock) {
+                        if (mSatelliteEnabledRequest != null &&
+                                mSatelliteEnabledRequest.enableSatellite == true &&
+                                argument.enableSatellite == false && mWaitingForRadioDisabled) {
+                            // Previous mSatelliteEnabledRequest is successful but waiting for
+                            // all radios to be turned off.
+                            mSatelliteEnabledRequest.callback.accept(
+                                    SatelliteManager.SATELLITE_ERROR_NONE);
+                        }
+                    }
+                    resetSatelliteEnabledRequest();
+
+                    // If Satellite enable/disable request returned Error, no need to wait for radio
+                    argument.callback.accept(error);
+                }
+
+                if (argument.enableSatellite) {
+                    if (error == SatelliteManager.SATELLITE_ERROR_NONE) {
+                        mControllerMetricsStats.onSatelliteEnabled();
+                        mControllerMetricsStats.reportServiceEnablementSuccessCount();
+                    } else {
+                        mControllerMetricsStats.reportServiceEnablementFailCount();
+                    }
+                    SessionMetricsStats.getInstance()
+                            .setInitializationResult(error)
+                            .setRadioTechnology(SatelliteManager.NT_RADIO_TECHNOLOGY_PROPRIETARY)
+                            .reportSessionMetrics();
+                } else {
+                    mControllerMetricsStats.onSatelliteDisabled();
+                }
+                break;
+            }
+
+            case CMD_IS_SATELLITE_ENABLED: {
+                request = (SatelliteControllerHandlerRequest) msg.obj;
+                onCompleted = obtainMessage(EVENT_IS_SATELLITE_ENABLED_DONE, request);
+                if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
+                    mSatelliteModemInterface.requestIsSatelliteEnabled(onCompleted);
+                    break;
+                }
+                Phone phone = request.phone;
+                if (phone != null) {
+                    phone.isSatellitePowerOn(onCompleted);
+                } else {
+                    loge("isSatelliteEnabled: No phone object");
+                    ((ResultReceiver) request.argument).send(
+                            SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null);
+                }
+                break;
+            }
+
+            case EVENT_IS_SATELLITE_ENABLED_DONE: {
+                ar = (AsyncResult) msg.obj;
+                request = (SatelliteControllerHandlerRequest) ar.userObj;
+                int error =  SatelliteServiceUtils.getSatelliteError(ar,
+                        "isSatelliteEnabled");
+                Bundle bundle = new Bundle();
+                if (error == SatelliteManager.SATELLITE_ERROR_NONE) {
+                    if (ar.result == null) {
+                        loge("isSatelliteEnabled: result is null");
+                        error = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE;
+                    } else {
+                        boolean enabled = ((int[]) ar.result)[0] == 1;
+                        if (DBG) logd("isSatelliteEnabled: " + enabled);
+                        bundle.putBoolean(SatelliteManager.KEY_SATELLITE_ENABLED, enabled);
+                        updateSatelliteEnabledState(enabled, "EVENT_IS_SATELLITE_ENABLED_DONE");
+                    }
+                } else if (error == SatelliteManager.SATELLITE_REQUEST_NOT_SUPPORTED) {
+                    updateSatelliteSupportedStateWhenSatelliteServiceConnected(false);
+                }
+                ((ResultReceiver) request.argument).send(error, bundle);
+                break;
+            }
+
+            case CMD_IS_SATELLITE_SUPPORTED: {
+                request = (SatelliteControllerHandlerRequest) msg.obj;
+                onCompleted = obtainMessage(EVENT_IS_SATELLITE_SUPPORTED_DONE, request);
+
+                if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
+                    mSatelliteModemInterface.requestIsSatelliteSupported(onCompleted);
+                    break;
+                }
+                Phone phone = request.phone;
+                if (phone != null) {
+                    phone.isSatelliteSupported(onCompleted);
+                } else {
+                    loge("isSatelliteSupported: No phone object");
+                    ((ResultReceiver) request.argument).send(
+                            SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null);
+                }
+                break;
+            }
+
+            case EVENT_IS_SATELLITE_SUPPORTED_DONE: {
+                ar = (AsyncResult) msg.obj;
+                request = (SatelliteControllerHandlerRequest) ar.userObj;
+                int error =  SatelliteServiceUtils.getSatelliteError(ar, "isSatelliteSupported");
+                Bundle bundle = new Bundle();
+                if (error == SatelliteManager.SATELLITE_ERROR_NONE) {
+                    if (ar.result == null) {
+                        loge("isSatelliteSupported: result is null");
+                        error = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE;
+                    } else {
+                        boolean supported = (boolean) ar.result;
+                        if (DBG) logd("isSatelliteSupported: " + supported);
+                        bundle.putBoolean(SatelliteManager.KEY_SATELLITE_SUPPORTED, supported);
+                        updateSatelliteSupportedStateWhenSatelliteServiceConnected(supported);
+                    }
+                }
+                ((ResultReceiver) request.argument).send(error, bundle);
+                break;
+            }
+
+            case CMD_GET_SATELLITE_CAPABILITIES: {
+                request = (SatelliteControllerHandlerRequest) msg.obj;
+                onCompleted = obtainMessage(EVENT_GET_SATELLITE_CAPABILITIES_DONE, request);
+                if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
+                    mSatelliteModemInterface.requestSatelliteCapabilities(onCompleted);
+                    break;
+                }
+                Phone phone = request.phone;
+                if (phone != null) {
+                    phone.getSatelliteCapabilities(onCompleted);
+                } else {
+                    loge("getSatelliteCapabilities: No phone object");
+                    ((ResultReceiver) request.argument).send(
+                            SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null);
+                }
+                break;
+            }
+
+            case EVENT_GET_SATELLITE_CAPABILITIES_DONE: {
+                ar = (AsyncResult) msg.obj;
+                request = (SatelliteControllerHandlerRequest) ar.userObj;
+                int error =  SatelliteServiceUtils.getSatelliteError(ar,
+                        "getSatelliteCapabilities");
+                Bundle bundle = new Bundle();
+                if (error == SatelliteManager.SATELLITE_ERROR_NONE) {
+                    if (ar.result == null) {
+                        loge("getSatelliteCapabilities: result is null");
+                        error = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE;
+                    } else {
+                        SatelliteCapabilities capabilities = (SatelliteCapabilities) ar.result;
+                        synchronized (mNeedsSatellitePointingLock) {
+                            mNeedsSatellitePointing = capabilities.isPointingRequired();
+                        }
+                        if (DBG) logd("getSatelliteCapabilities: " + capabilities);
+                        bundle.putParcelable(SatelliteManager.KEY_SATELLITE_CAPABILITIES,
+                                capabilities);
+                        synchronized (mSatelliteCapabilitiesLock) {
+                            mSatelliteCapabilities = capabilities;
+                        }
+                    }
+                }
+                ((ResultReceiver) request.argument).send(error, bundle);
+                break;
+            }
+
+            case CMD_IS_SATELLITE_COMMUNICATION_ALLOWED: {
+                request = (SatelliteControllerHandlerRequest) msg.obj;
+                onCompleted =
+                        obtainMessage(EVENT_IS_SATELLITE_COMMUNICATION_ALLOWED_DONE, request);
+                if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
+                    mSatelliteModemInterface
+                            .requestIsSatelliteCommunicationAllowedForCurrentLocation(
+                                    onCompleted);
+                    break;
+                }
+                Phone phone = request.phone;
+                if (phone != null) {
+                    phone.isSatelliteCommunicationAllowedForCurrentLocation(onCompleted);
+                } else {
+                    loge("isSatelliteCommunicationAllowedForCurrentLocation: No phone object");
+                    ((ResultReceiver) request.argument).send(
+                            SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null);
+                }
+                break;
+            }
+
+            case EVENT_IS_SATELLITE_COMMUNICATION_ALLOWED_DONE: {
+                ar = (AsyncResult) msg.obj;
+                request = (SatelliteControllerHandlerRequest) ar.userObj;
+                int error =  SatelliteServiceUtils.getSatelliteError(ar,
+                        "isSatelliteCommunicationAllowedForCurrentLocation");
+                Bundle bundle = new Bundle();
+                if (error == SatelliteManager.SATELLITE_ERROR_NONE) {
+                    if (ar.result == null) {
+                        loge("isSatelliteCommunicationAllowedForCurrentLocation: result is null");
+                        error = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE;
+                    } else {
+                        boolean communicationAllowed = (boolean) ar.result;
+                        if (DBG) {
+                            logd("isSatelliteCommunicationAllowedForCurrentLocation: "
+                                    + communicationAllowed);
+                        }
+                        bundle.putBoolean(SatelliteManager.KEY_SATELLITE_COMMUNICATION_ALLOWED,
+                                communicationAllowed);
+                    }
+                }
+                ((ResultReceiver) request.argument).send(error, bundle);
+                break;
+            }
+
+            case CMD_GET_TIME_SATELLITE_NEXT_VISIBLE: {
+                request = (SatelliteControllerHandlerRequest) msg.obj;
+                onCompleted = obtainMessage(EVENT_GET_TIME_SATELLITE_NEXT_VISIBLE_DONE,
+                        request);
+                if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
+                    mSatelliteModemInterface
+                            .requestTimeForNextSatelliteVisibility(onCompleted);
+                    break;
+                }
+                Phone phone = request.phone;
+                if (phone != null) {
+                    phone.requestTimeForNextSatelliteVisibility(onCompleted);
+                } else {
+                    loge("requestTimeForNextSatelliteVisibility: No phone object");
+                    ((ResultReceiver) request.argument).send(
+                            SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null);
+                }
+                break;
+            }
+
+            case EVENT_GET_TIME_SATELLITE_NEXT_VISIBLE_DONE: {
+                ar = (AsyncResult) msg.obj;
+                request = (SatelliteControllerHandlerRequest) ar.userObj;
+                int error = SatelliteServiceUtils.getSatelliteError(ar,
+                        "requestTimeForNextSatelliteVisibility");
+                Bundle bundle = new Bundle();
+                if (error == SatelliteManager.SATELLITE_ERROR_NONE) {
+                    if (ar.result == null) {
+                        loge("requestTimeForNextSatelliteVisibility: result is null");
+                        error = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE;
+                    } else {
+                        int nextVisibilityDuration = ((int[]) ar.result)[0];
+                        if (DBG) {
+                            logd("requestTimeForNextSatelliteVisibility: " +
+                                    nextVisibilityDuration);
+                        }
+                        bundle.putInt(SatelliteManager.KEY_SATELLITE_NEXT_VISIBILITY,
+                                nextVisibilityDuration);
+                    }
+                }
+                ((ResultReceiver) request.argument).send(error, bundle);
+                break;
+            }
+
+            case EVENT_RADIO_STATE_CHANGED: {
+                if (mCi.getRadioState() == TelephonyManager.RADIO_POWER_OFF
+                        || mCi.getRadioState() == TelephonyManager.RADIO_POWER_UNAVAILABLE) {
+                    mIsRadioOn = false;
+                    logd("Radio State Changed to " + mCi.getRadioState());
+                    IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() {
+                        @Override
+                        public void accept(int result) {
+                            logd("RequestSatelliteEnabled: result=" + result);
+                        }
+                    };
+                    Phone phone = SatelliteServiceUtils.getPhone();
+                    Consumer<Integer> result = FunctionalUtils
+                            .ignoreRemoteException(errorCallback::accept);
+                    RequestSatelliteEnabledArgument message =
+                            new RequestSatelliteEnabledArgument(false, false, result);
+                    request = new SatelliteControllerHandlerRequest(message, phone);
+                    handleSatelliteEnabled(request);
+                } else {
+                    mIsRadioOn = true;
+                    if (!mSatelliteModemInterface.isSatelliteServiceSupported()) {
+                        synchronized (mIsSatelliteSupportedLock) {
+                            if (mIsSatelliteSupported == null) {
+                                ResultReceiver receiver = new ResultReceiver(this) {
+                                    @Override
+                                    protected void onReceiveResult(
+                                            int resultCode, Bundle resultData) {
+                                        logd("requestIsSatelliteSupported: resultCode="
+                                                + resultCode);
+                                    }
+                                };
+                                requestIsSatelliteSupported(
+                                        SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, receiver);
+                            }
+                        }
+                    } else {
+                        logd("EVENT_RADIO_STATE_CHANGED: Satellite vendor service is supported."
+                                + " Ignored the event");
+                    }
+                }
+                break;
+            }
+
+            case CMD_IS_SATELLITE_PROVISIONED: {
+                request = (SatelliteControllerHandlerRequest) msg.obj;
+                onCompleted = obtainMessage(EVENT_IS_SATELLITE_PROVISIONED_DONE, request);
+                if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
+                    mSatelliteModemInterface.requestIsSatelliteProvisioned(onCompleted);
+                    break;
+                }
+                Phone phone = request.phone;
+                if (phone != null) {
+                    phone.isSatelliteProvisioned(onCompleted);
+                } else {
+                    loge("isSatelliteProvisioned: No phone object");
+                    ((ResultReceiver) request.argument).send(
+                            SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null);
+                }
+                break;
+            }
+
+            case EVENT_IS_SATELLITE_PROVISIONED_DONE: {
+                ar = (AsyncResult) msg.obj;
+                request = (SatelliteControllerHandlerRequest) ar.userObj;
+                int error =  SatelliteServiceUtils.getSatelliteError(ar,
+                        "isSatelliteProvisioned");
+                Bundle bundle = new Bundle();
+                if (error == SatelliteManager.SATELLITE_ERROR_NONE) {
+                    if (ar.result == null) {
+                        loge("isSatelliteProvisioned: result is null");
+                        error = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE;
+                    } else {
+                        boolean provisioned = ((int[]) ar.result)[0] == 1;
+                        if (DBG) logd("isSatelliteProvisioned: " + provisioned);
+                        bundle.putBoolean(SatelliteManager.KEY_SATELLITE_PROVISIONED, provisioned);
+                        synchronized (mIsSatelliteProvisionedLock) {
+                            mIsSatelliteProvisioned = provisioned;
+                        }
+                    }
+                }
+                ((ResultReceiver) request.argument).send(error, bundle);
+                break;
+            }
+
+            case EVENT_SATELLITE_PROVISION_STATE_CHANGED:
+                ar = (AsyncResult) msg.obj;
+                if (ar.result == null) {
+                    loge("EVENT_SATELLITE_PROVISION_STATE_CHANGED: result is null");
+                } else {
+                    handleEventSatelliteProvisionStateChanged((boolean) ar.result);
+                }
+                break;
+
+            case EVENT_PENDING_DATAGRAMS:
+                logd("Received EVENT_PENDING_DATAGRAMS");
+                IIntegerConsumer internalCallback = new IIntegerConsumer.Stub() {
+                    @Override
+                    public void accept(int result) {
+                        logd("pollPendingSatelliteDatagram result: " + result);
+                    }
+                };
+                pollPendingSatelliteDatagrams(
+                        SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, internalCallback);
+                break;
+
+            case EVENT_SATELLITE_MODEM_STATE_CHANGED:
+                ar = (AsyncResult) msg.obj;
+                if (ar.result == null) {
+                    loge("EVENT_SATELLITE_MODEM_STATE_CHANGED: result is null");
+                } else {
+                    handleEventSatelliteModemStateChanged((int) ar.result);
+                }
+                break;
+
+            default:
+                Log.w(TAG, "SatelliteControllerHandler: unexpected message code: " +
+                        msg.what);
+                break;
+        }
+    }
+
+    private void notifyRequester(SatelliteControllerHandlerRequest request) {
+        synchronized (request) {
+            request.notifyAll();
+        }
+    }
+
+    /**
+     * Request to enable or disable the satellite modem and demo mode. If the satellite modem is
+     * enabled, this will also disable the cellular modem, and if the satellite modem is disabled,
+     * this will also re-enable the cellular modem.
+     *
+     * @param subId The subId of the subscription to set satellite enabled for.
+     * @param enableSatellite {@code true} to enable the satellite modem and
+     *                        {@code false} to disable.
+     * @param enableDemoMode {@code true} to enable demo mode and {@code false} to disable.
+     * @param callback The callback to get the error code of the request.
+     */
+    public void requestSatelliteEnabled(int subId, boolean enableSatellite, boolean enableDemoMode,
+            @NonNull IIntegerConsumer callback) {
+        logd("requestSatelliteEnabled subId: " + subId + " enableSatellite: " + enableSatellite
+                + " enableDemoMode: " + enableDemoMode);
+
+        Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(callback::accept);
+
+        Boolean satelliteSupported = isSatelliteSupportedInternal();
+        if (satelliteSupported == null) {
+            result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+            return;
+        }
+        if (!satelliteSupported) {
+            result.accept(SatelliteManager.SATELLITE_NOT_SUPPORTED);
+            return;
+        }
+
+        Boolean satelliteProvisioned = isSatelliteProvisioned();
+        if (satelliteProvisioned == null) {
+            result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+            return;
+        }
+        if (!satelliteProvisioned) {
+            result.accept(SatelliteManager.SATELLITE_SERVICE_NOT_PROVISIONED);
+            return;
+        }
+
+        if (enableSatellite) {
+            if (!mIsRadioOn) {
+                loge("Radio is not on, can not enable satellite");
+                result.accept(SatelliteManager.SATELLITE_INVALID_MODEM_STATE);
+                return;
+            }
+        } else {
+            /* if disable satellite, always assume demo is also disabled */
+            enableDemoMode = false;
+        }
+
+        synchronized (mIsSatelliteEnabledLock) {
+            if (mIsSatelliteEnabled != null) {
+                if (mIsSatelliteEnabled == enableSatellite) {
+                    if (enableDemoMode != mIsDemoModeEnabled) {
+                        loge("Received invalid demo mode while satellite session is enabled"
+                                + " enableDemoMode = " + enableDemoMode);
+                        result.accept(SatelliteManager.SATELLITE_INVALID_ARGUMENTS);
+                        return;
+                    } else {
+                        logd("Enable request matches with current state"
+                                + " enableSatellite = " + enableSatellite);
+                        result.accept(SatelliteManager.SATELLITE_ERROR_NONE);
+                        return;
+                    }
+                }
+            }
+        }
+
+        RequestSatelliteEnabledArgument request =
+                new RequestSatelliteEnabledArgument(enableSatellite, enableDemoMode, result);
+        /**
+         * Multiple satellite enabled requests are handled as below:
+         * 1. If there are no ongoing requests, store current request in mSatelliteEnabledRequest
+         * 2. If there is a ongoing request, then:
+         *      1. ongoing request = enable, current request = enable: return IN_PROGRESS error
+         *      2. ongoing request = disable, current request = disable: return IN_PROGRESS error
+         *      3. ongoing request = disable, current request = enable: return SATELLITE_ERROR error
+         *      4. ongoing request = enable, current request = disable: send request to modem
+         */
+        synchronized (mSatelliteEnabledRequestLock) {
+            if (mSatelliteEnabledRequest == null) {
+                mSatelliteEnabledRequest = request;
+            } else if (mSatelliteEnabledRequest.enableSatellite == request.enableSatellite) {
+                logd("requestSatelliteEnabled  enableSatellite: " + enableSatellite
+                        + " is already in progress.");
+                result.accept(SatelliteManager.SATELLITE_REQUEST_IN_PROGRESS);
+                return;
+            } else if (mSatelliteEnabledRequest.enableSatellite == false
+                    && request.enableSatellite == true) {
+                logd("requestSatelliteEnabled  enableSatellite: " + enableSatellite + " cannot be "
+                        + "processed. Disable satellite is already in progress.");
+                result.accept(SatelliteManager.SATELLITE_ERROR);
+                return;
+            }
+        }
+
+        sendRequestAsync(CMD_SET_SATELLITE_ENABLED, request, SatelliteServiceUtils.getPhone());
+    }
+
+    /**
+     * Request to get whether the satellite modem is enabled.
+     *
+     * @param subId The subId of the subscription to check whether satellite is enabled for.
+     * @param result The result receiver that returns whether the satellite modem is enabled
+     *               if the request is successful or an error code if the request failed.
+     */
+    public void requestIsSatelliteEnabled(int subId, @NonNull ResultReceiver result) {
+        Boolean satelliteSupported = isSatelliteSupportedInternal();
+        if (satelliteSupported == null) {
+            result.send(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null);
+            return;
+        }
+        if (!satelliteSupported) {
+            result.send(SatelliteManager.SATELLITE_NOT_SUPPORTED, null);
+            return;
+        }
+
+        synchronized (mIsSatelliteEnabledLock) {
+            if (mIsSatelliteEnabled != null) {
+                /* We have already successfully queried the satellite modem. */
+                Bundle bundle = new Bundle();
+                bundle.putBoolean(SatelliteManager.KEY_SATELLITE_ENABLED, mIsSatelliteEnabled);
+                result.send(SatelliteManager.SATELLITE_ERROR_NONE, bundle);
+                return;
+            }
+        }
+
+        sendRequestAsync(CMD_IS_SATELLITE_ENABLED, result, SatelliteServiceUtils.getPhone());
+    }
+
+    /**
+     * Get whether the satellite modem is enabled.
+     * This will return the cached value instead of querying the satellite modem.
+     *
+     * @return {@code true} if the satellite modem is enabled and {@code false} otherwise.
+     */
+    public boolean isSatelliteEnabled() {
+        if (mIsSatelliteEnabled == null) return false;
+        return mIsSatelliteEnabled;
+    }
+
+    /**
+     * Request to get whether the satellite service demo mode is enabled.
+     *
+     * @param subId The subId of the subscription to check whether the satellite demo mode
+     *              is enabled for.
+     * @param result The result receiver that returns whether the satellite demo mode is enabled
+     *               if the request is successful or an error code if the request failed.
+     */
+    public void requestIsDemoModeEnabled(int subId, @NonNull ResultReceiver result) {
+        Boolean satelliteSupported = isSatelliteSupportedInternal();
+        if (satelliteSupported == null) {
+            result.send(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null);
+            return;
+        }
+        if (!satelliteSupported) {
+            result.send(SatelliteManager.SATELLITE_NOT_SUPPORTED, null);
+            return;
+        }
+
+        Boolean satelliteProvisioned = isSatelliteProvisioned();
+        if (satelliteProvisioned == null) {
+            result.send(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null);
+            return;
+        }
+        if (!satelliteProvisioned) {
+            result.send(SatelliteManager.SATELLITE_SERVICE_NOT_PROVISIONED, null);
+            return;
+        }
+
+        final Bundle bundle = new Bundle();
+        bundle.putBoolean(SatelliteManager.KEY_DEMO_MODE_ENABLED, mIsDemoModeEnabled);
+        result.send(SatelliteManager.SATELLITE_ERROR_NONE, bundle);
+    }
+
+    /**
+     * Get whether the satellite service demo mode is enabled.
+     *
+     * @return {@code true} if the satellite demo mode is enabled and {@code false} otherwise.
+     */
+    public boolean isDemoModeEnabled() {
+        return mIsDemoModeEnabled;
+    }
+
+    /**
+     * Request to get whether the satellite service is supported on the device.
+     *
+     * @param subId The subId of the subscription to check satellite service support for.
+     * @param result The result receiver that returns whether the satellite service is supported on
+     *               the device if the request is successful or an error code if the request failed.
+     */
+    public void requestIsSatelliteSupported(int subId, @NonNull ResultReceiver result) {
+        synchronized (mIsSatelliteSupportedLock) {
+            if (mIsSatelliteSupported != null) {
+                /* We have already successfully queried the satellite modem. */
+                Bundle bundle = new Bundle();
+                bundle.putBoolean(SatelliteManager.KEY_SATELLITE_SUPPORTED, mIsSatelliteSupported);
+                result.send(SatelliteManager.SATELLITE_ERROR_NONE, bundle);
+                return;
+            }
+        }
+
+        sendRequestAsync(CMD_IS_SATELLITE_SUPPORTED, result, SatelliteServiceUtils.getPhone());
+    }
+
+    /**
+     * Request to get the {@link SatelliteCapabilities} of the satellite service.
+     *
+     * @param subId The subId of the subscription to get the satellite capabilities for.
+     * @param result The result receiver that returns the {@link SatelliteCapabilities}
+     *               if the request is successful or an error code if the request failed.
+     */
+    public void requestSatelliteCapabilities(int subId, @NonNull ResultReceiver result) {
+        Boolean satelliteSupported = isSatelliteSupportedInternal();
+        if (satelliteSupported == null) {
+            result.send(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null);
+            return;
+        }
+        if (!satelliteSupported) {
+            result.send(SatelliteManager.SATELLITE_NOT_SUPPORTED, null);
+            return;
+        }
+
+        synchronized (mSatelliteCapabilitiesLock) {
+            if (mSatelliteCapabilities != null) {
+                Bundle bundle = new Bundle();
+                bundle.putParcelable(SatelliteManager.KEY_SATELLITE_CAPABILITIES,
+                        mSatelliteCapabilities);
+                result.send(SatelliteManager.SATELLITE_ERROR_NONE, bundle);
+                return;
+            }
+        }
+
+        sendRequestAsync(CMD_GET_SATELLITE_CAPABILITIES, result, SatelliteServiceUtils.getPhone());
+    }
+
+    /**
+     * Start receiving satellite transmission updates.
+     * This can be called by the pointing UI when the user starts pointing to the satellite.
+     * Modem should continue to report the pointing input as the device or satellite moves.
+     *
+     * @param subId The subId of the subscription to start satellite transmission updates for.
+     * @param errorCallback The callback to get the error code of the request.
+     * @param callback The callback to notify of satellite transmission updates.
+     */
+    public void startSatelliteTransmissionUpdates(int subId,
+            @NonNull IIntegerConsumer errorCallback,
+            @NonNull ISatelliteTransmissionUpdateCallback callback) {
+        Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(errorCallback::accept);
+        Boolean satelliteSupported = isSatelliteSupportedInternal();
+        if (satelliteSupported == null) {
+            result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+            return;
+        }
+        if (!satelliteSupported) {
+            result.accept(SatelliteManager.SATELLITE_NOT_SUPPORTED);
+            return;
+        }
+
+        Boolean satelliteProvisioned = isSatelliteProvisioned();
+        if (satelliteProvisioned == null) {
+            result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+            return;
+        }
+        if (!satelliteProvisioned) {
+            result.accept(SatelliteManager.SATELLITE_SERVICE_NOT_PROVISIONED);
+            return;
+        }
+
+        Phone phone = SatelliteServiceUtils.getPhone();
+        final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext);
+        mPointingAppController.registerForSatelliteTransmissionUpdates(validSubId, callback, phone);
+        sendRequestAsync(CMD_START_SATELLITE_TRANSMISSION_UPDATES,
+                new SatelliteTransmissionUpdateArgument(result, callback, validSubId), phone);
+    }
+
+    /**
+     * Stop receiving satellite transmission updates.
+     * This can be called by the pointing UI when the user stops pointing to the satellite.
+     *
+     * @param subId The subId of the subscription to stop satellite transmission updates for.
+     * @param errorCallback The callback to get the error code of the request.
+     * @param callback The callback that was passed to {@link #startSatelliteTransmissionUpdates(
+     *                 int, IIntegerConsumer, ISatelliteTransmissionUpdateCallback)}.
+     */
+    public void stopSatelliteTransmissionUpdates(int subId, @NonNull IIntegerConsumer errorCallback,
+            @NonNull ISatelliteTransmissionUpdateCallback callback) {
+        Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(errorCallback::accept);
+        Boolean satelliteSupported = isSatelliteSupportedInternal();
+        if (satelliteSupported == null) {
+            result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+            return;
+        }
+        if (!satelliteSupported) {
+            result.accept(SatelliteManager.SATELLITE_NOT_SUPPORTED);
+            return;
+        }
+
+        Boolean satelliteProvisioned = isSatelliteProvisioned();
+        if (satelliteProvisioned == null) {
+            result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+            return;
+        }
+        if (!satelliteProvisioned) {
+            result.accept(SatelliteManager.SATELLITE_SERVICE_NOT_PROVISIONED);
+            return;
+        }
+
+        Phone phone = SatelliteServiceUtils.getPhone();
+        final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext);
+        mPointingAppController.unregisterForSatelliteTransmissionUpdates(
+                validSubId, result, callback, phone);
+
+        // Even if handler is null - which means there are no listeners, the modem command to stop
+        // satellite transmission updates might have failed. The callers might want to retry
+        // sending the command. Thus, we always need to send this command to the modem.
+        sendRequestAsync(CMD_STOP_SATELLITE_TRANSMISSION_UPDATES, result, phone);
+    }
+
+    /**
+     * Register the subscription with a satellite provider.
+     * This is needed to register the subscription if the provider allows dynamic registration.
+     *
+     * @param subId The subId of the subscription to be provisioned.
+     * @param token The token to be used as a unique identifier for provisioning with satellite
+     *              gateway.
+     * @param provisionData Data from the provisioning app that can be used by provisioning server
+     * @param callback The callback to get the error code of the request.
+     *
+     * @return The signal transport used by the caller to cancel the provision request,
+     *         or {@code null} if the request failed.
+     */
+    @Nullable public ICancellationSignal provisionSatelliteService(int subId,
+            @NonNull String token, @NonNull byte[] provisionData,
+            @NonNull IIntegerConsumer callback) {
+        Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(callback::accept);
+        Boolean satelliteSupported = isSatelliteSupportedInternal();
+        if (satelliteSupported == null) {
+            result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+            return null;
+        }
+        if (!satelliteSupported) {
+            result.accept(SatelliteManager.SATELLITE_NOT_SUPPORTED);
+            return null;
+        }
+
+        final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext);
+        if (mSatelliteProvisionCallbacks.containsKey(validSubId)) {
+            result.accept(SatelliteManager.SATELLITE_SERVICE_PROVISION_IN_PROGRESS);
+            return null;
+        }
+
+        Boolean satelliteProvisioned = isSatelliteProvisioned();
+        if (satelliteProvisioned != null && satelliteProvisioned) {
+            result.accept(SatelliteManager.SATELLITE_ERROR_NONE);
+            return null;
+        }
+
+        Phone phone = SatelliteServiceUtils.getPhone();
+        sendRequestAsync(CMD_PROVISION_SATELLITE_SERVICE,
+                new ProvisionSatelliteServiceArgument(token, provisionData, result, validSubId),
+                phone);
+
+        ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+        CancellationSignal.fromTransport(cancelTransport).setOnCancelListener(() -> {
+            sendRequestAsync(CMD_DEPROVISION_SATELLITE_SERVICE,
+                    new ProvisionSatelliteServiceArgument(token, provisionData, null,
+                            validSubId), phone);
+            mProvisionMetricsStats.setIsCanceled(true);
+        });
+        return cancelTransport;
+    }
+
+    /**
+     * Unregister the device/subscription with the satellite provider.
+     * This is needed if the provider allows dynamic registration. Once deprovisioned,
+     * {@link android.telephony.satellite.SatelliteProvisionStateCallback
+     * #onSatelliteProvisionStateChanged(boolean)}
+     * should report as deprovisioned.
+     *
+     * @param subId The subId of the subscription to be deprovisioned.
+     * @param token The token of the device/subscription to be deprovisioned.
+     * @param callback The callback to get the error code of the request.
+     */
+    public void deprovisionSatelliteService(int subId,
+            @NonNull String token, @NonNull IIntegerConsumer callback) {
+        Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(callback::accept);
+        Boolean satelliteSupported = isSatelliteSupportedInternal();
+        if (satelliteSupported == null) {
+            result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+            return;
+        }
+        if (!satelliteSupported) {
+            result.accept(SatelliteManager.SATELLITE_NOT_SUPPORTED);
+            return;
+        }
+
+        Boolean satelliteProvisioned = isSatelliteProvisioned();
+        if (satelliteProvisioned == null) {
+            result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+            return;
+        }
+        if (!satelliteProvisioned) {
+            result.accept(SatelliteManager.SATELLITE_ERROR_NONE);
+            return;
+        }
+
+        Phone phone = SatelliteServiceUtils.getPhone();
+        final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext);
+        sendRequestAsync(CMD_DEPROVISION_SATELLITE_SERVICE,
+                new ProvisionSatelliteServiceArgument(token, null, result, validSubId),
+                phone);
+    }
+
+    /**
+     * Registers for the satellite provision state changed.
+     *
+     * @param subId The subId of the subscription to register for provision state changed.
+     * @param callback The callback to handle the satellite provision state changed event.
+     *
+     * @return The {@link SatelliteManager.SatelliteError} result of the operation.
+     */
+    @SatelliteManager.SatelliteError public int registerForSatelliteProvisionStateChanged(int subId,
+            @NonNull ISatelliteProvisionStateCallback callback) {
+        Boolean satelliteSupported = isSatelliteSupportedInternal();
+        if (satelliteSupported == null) {
+            return SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE;
+        }
+        if (!satelliteSupported) {
+            return SatelliteManager.SATELLITE_NOT_SUPPORTED;
+        }
+
+        mSatelliteProvisionStateChangedListeners.put(callback.asBinder(), callback);
+        return SatelliteManager.SATELLITE_ERROR_NONE;
+    }
+
+    /**
+     * Unregisters for the satellite provision state changed.
+     * If callback was not registered before, the request will be ignored.
+     *
+     * @param subId The subId of the subscription to unregister for provision state changed.
+     * @param callback The callback that was passed to
+     * {@link #registerForSatelliteProvisionStateChanged(int, ISatelliteProvisionStateCallback)}.
+     */
+    public void unregisterForSatelliteProvisionStateChanged(
+            int subId, @NonNull ISatelliteProvisionStateCallback callback) {
+        mSatelliteProvisionStateChangedListeners.remove(callback.asBinder());
+    }
+
+    /**
+     * Request to get whether the device is provisioned with a satellite provider.
+     *
+     * @param subId The subId of the subscription to get whether the device is provisioned for.
+     * @param result The result receiver that returns whether the device is provisioned with a
+     *               satellite provider if the request is successful or an error code if the
+     *               request failed.
+     */
+    public void requestIsSatelliteProvisioned(int subId, @NonNull ResultReceiver result) {
+        Boolean satelliteSupported = isSatelliteSupportedInternal();
+        if (satelliteSupported == null) {
+            result.send(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null);
+            return;
+        }
+        if (!satelliteSupported) {
+            result.send(SatelliteManager.SATELLITE_NOT_SUPPORTED, null);
+            return;
+        }
+
+        synchronized (mIsSatelliteProvisionedLock) {
+            if (mIsSatelliteProvisioned != null) {
+                Bundle bundle = new Bundle();
+                bundle.putBoolean(SatelliteManager.KEY_SATELLITE_PROVISIONED,
+                        mIsSatelliteProvisioned);
+                result.send(SatelliteManager.SATELLITE_ERROR_NONE, bundle);
+                return;
+            }
+        }
+
+        sendRequestAsync(CMD_IS_SATELLITE_PROVISIONED, result, SatelliteServiceUtils.getPhone());
+    }
+
+    /**
+     * Registers for modem state changed from satellite modem.
+     *
+     * @param subId The subId of the subscription to register for satellite modem state changed.
+     * @param callback The callback to handle the satellite modem state changed event.
+     *
+     * @return The {@link SatelliteManager.SatelliteError} result of the operation.
+     */
+    @SatelliteManager.SatelliteError public int registerForSatelliteModemStateChanged(int subId,
+            @NonNull ISatelliteStateCallback callback) {
+        if (mSatelliteSessionController != null) {
+            mSatelliteSessionController.registerForSatelliteModemStateChanged(callback);
+        } else {
+            loge("registerForSatelliteModemStateChanged: mSatelliteSessionController"
+                    + " is not initialized yet");
+            return SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE;
+        }
+        return SatelliteManager.SATELLITE_ERROR_NONE;
+    }
+
+    /**
+     * Unregisters for modem state changed from satellite modem.
+     * If callback was not registered before, the request will be ignored.
+     *
+     * @param subId The subId of the subscription to unregister for satellite modem state changed.
+     * @param callback The callback that was passed to
+     *                 {@link #registerForSatelliteModemStateChanged(int, ISatelliteStateCallback)}.
+     */
+    public void unregisterForSatelliteModemStateChanged(int subId,
+            @NonNull ISatelliteStateCallback callback) {
+        if (mSatelliteSessionController != null) {
+            mSatelliteSessionController.unregisterForSatelliteModemStateChanged(callback);
+        } else {
+            loge("registerForSatelliteModemStateChanged: mSatelliteSessionController"
+                    + " is not initialized yet");
+        }
+    }
+
+    /**
+     * Register to receive incoming datagrams over satellite.
+     *
+     * @param subId The subId of the subscription to register for incoming satellite datagrams.
+     * @param callback The callback to handle incoming datagrams over satellite.
+     *
+     * @return The {@link SatelliteManager.SatelliteError} result of the operation.
+     */
+    @SatelliteManager.SatelliteError public int registerForSatelliteDatagram(int subId,
+            @NonNull ISatelliteDatagramCallback callback) {
+        return mDatagramController.registerForSatelliteDatagram(subId, callback);
+    }
+
+    /**
+     * Unregister to stop receiving incoming datagrams over satellite.
+     * If callback was not registered before, the request will be ignored.
+     *
+     * @param subId The subId of the subscription to unregister for incoming satellite datagrams.
+     * @param callback The callback that was passed to
+     *                 {@link #registerForSatelliteDatagram(int, ISatelliteDatagramCallback)}.
+     */
+    public void unregisterForSatelliteDatagram(int subId,
+            @NonNull ISatelliteDatagramCallback callback) {
+        mDatagramController.unregisterForSatelliteDatagram(subId, callback);
+    }
+
+    /**
+     * Poll pending satellite datagrams over satellite.
+     *
+     * This method requests modem to check if there are any pending datagrams to be received over
+     * satellite. If there are any incoming datagrams, they will be received via
+     * {@link android.telephony.satellite.SatelliteDatagramCallback#onSatelliteDatagramReceived(
+     * long, SatelliteDatagram, int, Consumer)}
+     *
+     * @param subId The subId of the subscription used for receiving datagrams.
+     * @param callback The callback to get {@link SatelliteManager.SatelliteError} of the request.
+     */
+    public void pollPendingSatelliteDatagrams(int subId, @NonNull IIntegerConsumer callback) {
+        Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(callback::accept);
+
+        Boolean satelliteProvisioned = isSatelliteProvisioned();
+        if (satelliteProvisioned == null) {
+            result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+            return;
+        }
+        if (!satelliteProvisioned) {
+            result.accept(SatelliteManager.SATELLITE_SERVICE_NOT_PROVISIONED);
+            return;
+        }
+
+        final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext);
+        mDatagramController.pollPendingSatelliteDatagrams(validSubId, result);
+    }
+
+    /**
+     * Send datagram over satellite.
+     *
+     * Gateway encodes SOS message or location sharing message into a datagram and passes it as
+     * input to this method. Datagram received here will be passed down to modem without any
+     * encoding or encryption.
+     *
+     * @param subId The subId of the subscription to send satellite datagrams for.
+     * @param datagramType datagram type indicating whether the datagram is of type
+     *                     SOS_SMS or LOCATION_SHARING.
+     * @param datagram encoded gateway datagram which is encrypted by the caller.
+     *                 Datagram will be passed down to modem without any encoding or encryption.
+     * @param needFullScreenPointingUI this is used to indicate pointingUI app to open in
+     *                                 full screen mode.
+     * @param callback The callback to get {@link SatelliteManager.SatelliteError} of the request.
+     */
+    public void sendSatelliteDatagram(int subId, @SatelliteManager.DatagramType int datagramType,
+            SatelliteDatagram datagram, boolean needFullScreenPointingUI,
+            @NonNull IIntegerConsumer callback) {
+        Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(callback::accept);
+
+        Boolean satelliteProvisioned = isSatelliteProvisioned();
+        if (satelliteProvisioned == null) {
+            result.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+            return;
+        }
+        if (!satelliteProvisioned) {
+            result.accept(SatelliteManager.SATELLITE_SERVICE_NOT_PROVISIONED);
+            return;
+        }
+
+        /**
+         * TODO for NTN-based satellites: Check if satellite is acquired.
+         */
+        if (mNeedsSatellitePointing) {
+            mPointingAppController.startPointingUI(needFullScreenPointingUI);
+        }
+
+        final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext);
+        mDatagramController.sendSatelliteDatagram(validSubId, datagramType, datagram,
+                needFullScreenPointingUI, result);
+    }
+
+    /**
+     * Request to get whether satellite communication is allowed for the current location.
+     *
+     * @param subId The subId of the subscription to check whether satellite communication is
+     *              allowed for the current location for.
+     * @param result The result receiver that returns whether satellite communication is allowed
+     *               for the current location if the request is successful or an error code
+     *               if the request failed.
+     */
+    public void requestIsSatelliteCommunicationAllowedForCurrentLocation(int subId,
+            @NonNull ResultReceiver result) {
+        Boolean satelliteSupported = isSatelliteSupportedInternal();
+        if (satelliteSupported == null) {
+            result.send(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null);
+            return;
+        }
+        if (!satelliteSupported) {
+            result.send(SatelliteManager.SATELLITE_NOT_SUPPORTED, null);
+            return;
+        }
+
+        sendRequestAsync(
+                CMD_IS_SATELLITE_COMMUNICATION_ALLOWED, result, SatelliteServiceUtils.getPhone());
+    }
+
+    /**
+     * Request to get the time after which the satellite will be visible.
+     *
+     * @param subId The subId to get the time after which the satellite will be visible for.
+     * @param result The result receiver that returns the time after which the satellite will
+     *               be visible if the request is successful or an error code if the request failed.
+     */
+    public void requestTimeForNextSatelliteVisibility(int subId, @NonNull ResultReceiver result) {
+        Boolean satelliteSupported = isSatelliteSupportedInternal();
+        if (satelliteSupported == null) {
+            result.send(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null);
+            return;
+        }
+        if (!satelliteSupported) {
+            result.send(SatelliteManager.SATELLITE_NOT_SUPPORTED, null);
+            return;
+        }
+
+        Boolean satelliteProvisioned = isSatelliteProvisioned();
+        if (satelliteProvisioned == null) {
+            result.send(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE, null);
+            return;
+        }
+        if (!satelliteProvisioned) {
+            result.send(SatelliteManager.SATELLITE_SERVICE_NOT_PROVISIONED, null);
+            return;
+        }
+
+        Phone phone = SatelliteServiceUtils.getPhone();
+        sendRequestAsync(CMD_GET_TIME_SATELLITE_NEXT_VISIBLE, result, phone);
+    }
+
+    /**
+     * Inform whether the device is aligned with satellite for demo mode.
+     *
+     * @param subId The subId of the subscription.
+     * @param isAligned {@true} means device is aligned with the satellite, otherwise {@false}.
+     */
+    public void onDeviceAlignedWithSatellite(@NonNull int subId, @NonNull boolean isAligned) {
+        mDatagramController.onDeviceAlignedWithSatellite(isAligned);
+    }
+
+    /**
+     * This API can be used by only CTS to update satellite vendor service package name.
+     *
+     * @param servicePackageName The package name of the satellite vendor service.
+     * @return {@code true} if the satellite vendor service is set successfully,
+     * {@code false} otherwise.
+     */
+    public boolean setSatelliteServicePackageName(@Nullable String servicePackageName) {
+        boolean result = mSatelliteModemInterface.setSatelliteServicePackageName(
+                servicePackageName);
+        if (result) {
+            logd("setSatelliteServicePackageName: Resetting cached states");
+
+            // Cached states need to be cleared whenever switching satellite vendor services.
+            synchronized (mIsSatelliteSupportedLock) {
+                mIsSatelliteSupported = null;
+            }
+            synchronized (mIsSatelliteProvisionedLock) {
+                mIsSatelliteProvisioned = null;
+            }
+            synchronized (mIsSatelliteEnabledLock) {
+                mIsSatelliteEnabled = null;
+            }
+            synchronized (mSatelliteCapabilitiesLock) {
+                mSatelliteCapabilities = null;
+            }
+            ResultReceiver receiver = new ResultReceiver(this) {
+                @Override
+                protected void onReceiveResult(int resultCode, Bundle resultData) {
+                    logd("requestIsSatelliteSupported: resultCode=" + resultCode);
+                }
+            };
+            requestIsSatelliteSupported(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, receiver);
+        }
+        return result;
+    }
+
+    /**
+     * This API can be used by only CTS to update the timeout duration in milliseconds that
+     * satellite should stay at listening mode to wait for the next incoming page before disabling
+     * listening mode.
+     *
+     * @param timeoutMillis The timeout duration in millisecond.
+     * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise.
+     */
+    public boolean setSatelliteListeningTimeoutDuration(long timeoutMillis) {
+        if (mSatelliteSessionController == null) {
+            loge("mSatelliteSessionController is not initialized yet");
+            return false;
+        }
+        return mSatelliteSessionController.setSatelliteListeningTimeoutDuration(timeoutMillis);
+    }
+
+    /**
+     * 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
+     *
+     * @param timeoutMillis The timeout duration in millisecond.
+     * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise.
+     */
+    public boolean setSatelliteDeviceAlignedTimeoutDuration(long timeoutMillis) {
+        return mDatagramController.setSatelliteDeviceAlignedTimeoutDuration(timeoutMillis);
+    }
+
+    /**
+     * This API can be used by only CTS to update satellite gateway service package name.
+     *
+     * @param servicePackageName The package name of the satellite gateway service.
+     * @return {@code true} if the satellite gateway service is set successfully,
+     * {@code false} otherwise.
+     */
+    public boolean setSatelliteGatewayServicePackageName(@Nullable String servicePackageName) {
+        if (mSatelliteSessionController == null) {
+            loge("mSatelliteSessionController is not initialized yet");
+            return false;
+        }
+        return mSatelliteSessionController.setSatelliteGatewayServicePackageName(
+                servicePackageName);
+    }
+
+    /**
+     * This API can be used by only CTS to update satellite pointing UI app package and class names.
+     *
+     * @param packageName The package name of the satellite pointing UI app.
+     * @param className The class name of the satellite pointing UI app.
+     * @return {@code true} if the satellite pointing UI app package and class is set successfully,
+     * {@code false} otherwise.
+     */
+    public boolean setSatellitePointingUiClassName(
+            @Nullable String packageName, @Nullable String className) {
+        return mPointingAppController.setSatellitePointingUiClassName(packageName, className);
+    }
+
+    /**
+     * This function is used by {@link SatelliteModemInterface} to notify
+     * {@link SatelliteController} that the satellite vendor service was just connected.
+     * <p>
+     * {@link SatelliteController} will send requests to satellite modem to check whether it support
+     * satellite and whether it is provisioned. {@link SatelliteController} will use these cached
+     * values to serve requests from its clients.
+     * <p>
+     * Because satellite vendor service might have just come back from a crash, we need to disable
+     * the satellite modem so that resources will be cleaned up and internal states will be reset.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void onSatelliteServiceConnected() {
+        if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
+            synchronized (mIsSatelliteSupportedLock) {
+                if (mIsSatelliteSupported == null) {
+                    ResultReceiver receiver = new ResultReceiver(this) {
+                        @Override
+                        protected void onReceiveResult(
+                                int resultCode, Bundle resultData) {
+                            logd("requestIsSatelliteSupported: resultCode="
+                                    + resultCode);
+                        }
+                    };
+                    requestIsSatelliteSupported(
+                            SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, receiver);
+                }
+            }
+        } else {
+            logd("onSatelliteServiceConnected: Satellite vendor service is not supported."
+                    + " Ignored the event");
+        }
+    }
+
+    /**
+     * @return {@code true} is satellite is supported on the device, {@code  false} otherwise.
+     */
+    public boolean isSatelliteSupported() {
+        Boolean supported = isSatelliteSupportedInternal();
+        return (supported != null ? supported : false);
+    }
+
+    /**
+     * If we have not successfully queried the satellite modem for its satellite service support,
+     * we will retry the query one more time. Otherwise, we will return the cached result.
+     */
+    private Boolean isSatelliteSupportedInternal() {
+        synchronized (mIsSatelliteSupportedLock) {
+            if (mIsSatelliteSupported != null) {
+                /* We have already successfully queried the satellite modem. */
+                return mIsSatelliteSupported;
+            }
+        }
+        /**
+         * We have not successfully checked whether the modem supports satellite service.
+         * Thus, we need to retry it now.
+         */
+        requestIsSatelliteSupported(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                new ResultReceiver(this) {
+                    @Override
+                    protected void onReceiveResult(int resultCode, Bundle resultData) {
+                        logd("requestIsSatelliteSupported: resultCode=" + resultCode);
+                    }
+                });
+        return null;
+    }
+
+    private void handleEventProvisionSatelliteServiceDone(
+            @NonNull ProvisionSatelliteServiceArgument arg,
+            @SatelliteManager.SatelliteError int result) {
+        logd("handleEventProvisionSatelliteServiceDone: result="
+                + result + ", subId=" + arg.subId);
+
+        Consumer<Integer> callback = mSatelliteProvisionCallbacks.remove(arg.subId);
+        if (callback == null) {
+            loge("handleEventProvisionSatelliteServiceDone: callback is null for subId="
+                    + arg.subId);
+            mProvisionMetricsStats
+                    .setResultCode(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE)
+                    .setIsProvisionRequest(true)
+                    .reportProvisionMetrics();
+            mControllerMetricsStats.reportProvisionCount(
+                    SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+            return;
+        }
+        callback.accept(result);
+    }
+
+    private void handleEventDeprovisionSatelliteServiceDone(
+            @NonNull ProvisionSatelliteServiceArgument arg,
+            @SatelliteManager.SatelliteError int result) {
+        if (arg == null) {
+            loge("handleEventDeprovisionSatelliteServiceDone: arg is null");
+            return;
+        }
+        logd("handleEventDeprovisionSatelliteServiceDone: result="
+                + result + ", subId=" + arg.subId);
+
+        if (arg.callback != null) {
+            arg.callback.accept(result);
+            mProvisionMetricsStats.setResultCode(result)
+                    .setIsProvisionRequest(false)
+                    .reportProvisionMetrics();
+            mControllerMetricsStats.reportDeprovisionCount(result);
+        }
+    }
+
+    private void handleStartSatelliteTransmissionUpdatesDone(@NonNull AsyncResult ar) {
+        SatelliteControllerHandlerRequest request = (SatelliteControllerHandlerRequest) ar.userObj;
+        SatelliteTransmissionUpdateArgument arg =
+                (SatelliteTransmissionUpdateArgument) request.argument;
+        int errorCode =  SatelliteServiceUtils.getSatelliteError(ar,
+                "handleStartSatelliteTransmissionUpdatesDone");
+        arg.errorCallback.accept(errorCode);
+
+        if (errorCode != SatelliteManager.SATELLITE_ERROR_NONE) {
+            mPointingAppController.setStartedSatelliteTransmissionUpdates(false);
+            // We need to remove the callback from our listener list since the caller might not call
+            // stopSatelliteTransmissionUpdates to unregister the callback in case of failure.
+            mPointingAppController.unregisterForSatelliteTransmissionUpdates(arg.subId,
+                    arg.errorCallback, arg.callback, request.phone);
+        } else {
+            mPointingAppController.setStartedSatelliteTransmissionUpdates(true);
+        }
+    }
+
+    /**
+     * 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
+     * @param phone phone object used to perform the operation.
+     */
+    private void sendRequestAsync(int command, @NonNull Object argument, @Nullable Phone phone) {
+        SatelliteControllerHandlerRequest request = new SatelliteControllerHandlerRequest(
+                argument, phone);
+        Message msg = this.obtainMessage(command, request);
+        msg.sendToTarget();
+    }
+
+    /**
+     * Posts the specified command to be executed on the main thread. As this is a synchronous
+     * request, it waits until the request is complete and then return the result.
+     *
+     * @param command command to be executed on the main thread
+     * @param argument additional parameters required to perform of the operation
+     * @param phone phone object used to perform the operation.
+     * @return result of the operation
+     */
+    private @Nullable Object sendRequest(int command, @NonNull Object argument,
+            @Nullable Phone phone) {
+        if (Looper.myLooper() == this.getLooper()) {
+            throw new RuntimeException("This method will deadlock if called from the main thread");
+        }
+
+        SatelliteControllerHandlerRequest request = new SatelliteControllerHandlerRequest(
+                argument, phone);
+        Message msg = this.obtainMessage(command, request);
+        msg.sendToTarget();
+
+        synchronized (request) {
+            while(request.result == null) {
+                try {
+                    request.wait();
+                } catch (InterruptedException e) {
+                    // Do nothing, go back and wait until the request is complete.
+                }
+            }
+        }
+        return request.result;
+    }
+
+    /**
+     * Check if satellite is provisioned for a subscription on the device.
+     * @return true if satellite is provisioned on the given subscription else return false.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    protected Boolean isSatelliteProvisioned() {
+        synchronized (mIsSatelliteProvisionedLock) {
+            if (mIsSatelliteProvisioned != null) {
+                return mIsSatelliteProvisioned;
+            }
+        }
+
+        requestIsSatelliteProvisioned(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                new ResultReceiver(this) {
+                    @Override
+                    protected void onReceiveResult(int resultCode, Bundle resultData) {
+                        logd("requestIsSatelliteProvisioned: resultCode=" + resultCode);
+                    }
+                });
+        return null;
+    }
+
+    private void handleSatelliteEnabled(SatelliteControllerHandlerRequest request) {
+        RequestSatelliteEnabledArgument argument =
+                (RequestSatelliteEnabledArgument) request.argument;
+        Message onCompleted = obtainMessage(EVENT_SET_SATELLITE_ENABLED_DONE, request);
+        if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
+            mSatelliteModemInterface.requestSatelliteEnabled(argument.enableSatellite,
+                    argument.enableDemoMode, onCompleted);
+            return;
+        }
+        Phone phone = request.phone;
+        if (phone != null) {
+            phone.setSatellitePower(onCompleted, argument.enableSatellite);
+        } else {
+            loge("requestSatelliteEnabled: No phone object");
+            argument.callback.accept(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+        }
+    }
+
+    private void updateSatelliteSupportedStateWhenSatelliteServiceConnected(boolean supported) {
+        synchronized (mIsSatelliteSupportedLock) {
+            mIsSatelliteSupported = supported;
+        }
+        mSatelliteSessionController = SatelliteSessionController.make(
+                mContext, getLooper(), supported);
+        if (supported) {
+            registerForSatelliteProvisionStateChanged();
+            registerForPendingDatagramCount();
+            registerForSatelliteModemStateChanged();
+
+            requestIsSatelliteProvisioned(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                    new ResultReceiver(this) {
+                        @Override
+                        protected void onReceiveResult(int resultCode, Bundle resultData) {
+                            logd("requestIsSatelliteProvisioned: resultCode=" + resultCode);
+                            requestSatelliteEnabled(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                                    false, false,
+                                    new IIntegerConsumer.Stub() {
+                                        @Override
+                                        public void accept(int result) {
+                                            logd("requestSatelliteEnabled: result=" + result);
+                                        }
+                                    });
+                        }
+                    });
+            requestSatelliteCapabilities(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                    new ResultReceiver(this) {
+                        @Override
+                        protected void onReceiveResult(int resultCode, Bundle resultData) {
+                            logd("requestSatelliteCapabilities: resultCode=" + resultCode);
+                        }
+                    });
+        }
+    }
+
+    private void updateSatelliteEnabledState(boolean enabled, String caller) {
+        synchronized (mIsSatelliteEnabledLock) {
+            mIsSatelliteEnabled = enabled;
+        }
+        if (mSatelliteSessionController != null) {
+            mSatelliteSessionController.onSatelliteEnabledStateChanged(enabled);
+            mSatelliteSessionController.setDemoMode(mIsDemoModeEnabled);
+        } else {
+            loge(caller + ": mSatelliteSessionController is not initialized yet");
+        }
+    }
+
+    private void registerForSatelliteProvisionStateChanged() {
+        if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
+            if (!mRegisteredForProvisionStateChangedWithSatelliteService.get()) {
+                mSatelliteModemInterface.registerForSatelliteProvisionStateChanged(
+                        this, EVENT_SATELLITE_PROVISION_STATE_CHANGED, null);
+                mRegisteredForProvisionStateChangedWithSatelliteService.set(true);
+            }
+        } else {
+            Phone phone = SatelliteServiceUtils.getPhone();
+            if (phone == null) {
+                loge("registerForSatelliteProvisionStateChanged: phone is null");
+            } else if (!mRegisteredForProvisionStateChangedWithPhone.get()) {
+                phone.registerForSatelliteProvisionStateChanged(
+                        this, EVENT_SATELLITE_PROVISION_STATE_CHANGED, null);
+                mRegisteredForProvisionStateChangedWithPhone.set(true);
+            }
+        }
+    }
+
+    private void registerForPendingDatagramCount() {
+        if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
+            if (!mRegisteredForPendingDatagramCountWithSatelliteService.get()) {
+                mSatelliteModemInterface.registerForPendingDatagrams(
+                        this, EVENT_PENDING_DATAGRAMS, null);
+                mRegisteredForPendingDatagramCountWithSatelliteService.set(true);
+            }
+        } else {
+            Phone phone = SatelliteServiceUtils.getPhone();
+            if (phone == null) {
+                loge("registerForPendingDatagramCount: satellite phone is "
+                        + "not initialized yet");
+            } else if (!mRegisteredForPendingDatagramCountWithPhone.get()) {
+                phone.registerForPendingDatagramCount(this, EVENT_PENDING_DATAGRAMS, null);
+                mRegisteredForPendingDatagramCountWithPhone.set(true);
+            }
+        }
+    }
+
+    private void registerForSatelliteModemStateChanged() {
+        if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
+            if (!mRegisteredForSatelliteModemStateChangedWithSatelliteService.get()) {
+                mSatelliteModemInterface.registerForSatelliteModemStateChanged(
+                        this, EVENT_SATELLITE_MODEM_STATE_CHANGED, null);
+                mRegisteredForSatelliteModemStateChangedWithSatelliteService.set(true);
+            }
+        } else {
+            Phone phone = SatelliteServiceUtils.getPhone();
+            if (phone == null) {
+                loge("registerForSatelliteModemStateChanged: satellite phone is "
+                        + "not initialized yet");
+            } else if (!mRegisteredForSatelliteModemStateChangedWithPhone.get()) {
+                phone.registerForSatelliteModemStateChanged(
+                        this, EVENT_SATELLITE_MODEM_STATE_CHANGED, null);
+                mRegisteredForSatelliteModemStateChangedWithPhone.set(true);
+            }
+        }
+    }
+
+    private void handleEventSatelliteProvisionStateChanged(boolean provisioned) {
+        logd("handleSatelliteProvisionStateChangedEvent: provisioned=" + provisioned);
+
+        synchronized (mIsSatelliteProvisionedLock) {
+            mIsSatelliteProvisioned = provisioned;
+        }
+
+        List<ISatelliteProvisionStateCallback> toBeRemoved = new ArrayList<>();
+        mSatelliteProvisionStateChangedListeners.values().forEach(listener -> {
+            try {
+                listener.onSatelliteProvisionStateChanged(provisioned);
+            } catch (RemoteException e) {
+                logd("handleSatelliteProvisionStateChangedEvent RemoteException: " + e);
+                toBeRemoved.add(listener);
+            }
+        });
+        toBeRemoved.forEach(listener -> {
+            mSatelliteProvisionStateChangedListeners.remove(listener.asBinder());
+        });
+    }
+
+    private void handleEventSatelliteModemStateChanged(
+            @SatelliteManager.SatelliteModemState int state) {
+        logd("handleEventSatelliteModemStateChanged: state=" + state);
+        if (state == SatelliteManager.SATELLITE_MODEM_STATE_OFF
+                || state == SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE) {
+            setSettingsKeyForSatelliteMode(SATELLITE_MODE_ENABLED_FALSE);
+            setDemoModeEnabled(false);
+            updateSatelliteEnabledState(
+                    false, "handleEventSatelliteModemStateChanged");
+            cleanUpResources(state);
+        }
+
+        mDatagramController.onSatelliteModemStateChanged(state);
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected void setSettingsKeyForSatelliteMode(int val) {
+        logd("setSettingsKeyForSatelliteMode val: " + val);
+        Settings.Global.putInt(mContext.getContentResolver(),
+                    Settings.Global.SATELLITE_MODE_ENABLED, val);
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected boolean areAllRadiosDisabled() {
+        synchronized (mRadioStateLock) {
+            if ((mDisableBTOnSatelliteEnabled && mBTStateEnabled)
+                    || (mDisableNFCOnSatelliteEnabled && mNfcStateEnabled)
+                    || (mDisableWifiOnSatelliteEnabled && mWifiStateEnabled)
+                    || (mDisableUWBOnSatelliteEnabled && mUwbStateEnabled)) {
+                logd("All radios are not disabled yet.");
+                return false;
+            }
+            logd("All radios are disabled.");
+            return true;
+        }
+    }
+
+    private void evaluateToSendSatelliteEnabledSuccess() {
+        logd("evaluateToSendSatelliteEnabledSuccess");
+        synchronized (mSatelliteEnabledRequestLock) {
+            if (areAllRadiosDisabled() && (mSatelliteEnabledRequest != null)
+                    && mWaitingForRadioDisabled) {
+                logd("Sending success to callback that sent enable satellite request");
+                setDemoModeEnabled(mSatelliteEnabledRequest.enableDemoMode);
+                synchronized (mIsSatelliteEnabledLock) {
+                    mIsSatelliteEnabled = mSatelliteEnabledRequest.enableSatellite;
+                }
+                mSatelliteEnabledRequest.callback.accept(SatelliteManager.SATELLITE_ERROR_NONE);
+                updateSatelliteEnabledState(
+                        mSatelliteEnabledRequest.enableSatellite,
+                        "EVENT_SET_SATELLITE_ENABLED_DONE");
+                mSatelliteEnabledRequest = null;
+                mWaitingForRadioDisabled = false;
+            }
+        }
+    }
+
+    private void resetSatelliteEnabledRequest() {
+        logd("resetSatelliteEnabledRequest");
+        synchronized (mSatelliteEnabledRequestLock) {
+            mSatelliteEnabledRequest = null;
+            mWaitingForRadioDisabled = false;
+        }
+    }
+
+    private void cleanUpResources(@SatelliteManager.SatelliteModemState int state) {
+        logd("cleanUpResources");
+        if (state == SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE) {
+            synchronized (mSatelliteEnabledRequestLock) {
+                if (mSatelliteEnabledRequest != null) {
+                    mSatelliteEnabledRequest.callback.accept(
+                            SatelliteManager.SATELLITE_INVALID_MODEM_STATE);
+                }
+            }
+            resetSatelliteEnabledRequest();
+        }
+    }
+
+    private void setDemoModeEnabled(boolean enabled) {
+        mIsDemoModeEnabled = enabled;
+        mDatagramController.setDemoMode(mIsDemoModeEnabled);
+    }
+
+    private static void logd(@NonNull String log) {
+        Rlog.d(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
new file mode 100644
index 0000000..80c67b3
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java
@@ -0,0 +1,1069 @@
+/*
+ * 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.satellite;
+
+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.Binder;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RegistrantList;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.telephony.Rlog;
+import android.telephony.satellite.SatelliteCapabilities;
+import android.telephony.satellite.SatelliteDatagram;
+import android.telephony.satellite.SatelliteManager;
+import android.telephony.satellite.SatelliteManager.SatelliteException;
+import android.telephony.satellite.stub.ISatellite;
+import android.telephony.satellite.stub.ISatelliteCapabilitiesConsumer;
+import android.telephony.satellite.stub.ISatelliteListener;
+import android.telephony.satellite.stub.SatelliteService;
+import android.text.TextUtils;
+import android.util.Pair;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.ExponentialBackoff;
+import com.android.internal.telephony.IBooleanConsumer;
+import com.android.internal.telephony.IIntegerConsumer;
+
+import java.util.Arrays;
+
+/**
+ * Satellite modem interface to manage connections with the satellite service and HAL interface.
+ */
+public class SatelliteModemInterface {
+    private static final String TAG = "SatelliteModemInterface";
+    private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem";
+    private static final boolean DEBUG = !"user".equals(Build.TYPE);
+    private static final long REBIND_INITIAL_DELAY = 2 * 1000; // 2 seconds
+    private static final long REBIND_MAXIMUM_DELAY = 64 * 1000; // 1 minute
+    private static final int REBIND_MULTIPLIER = 2;
+
+    @NonNull private static SatelliteModemInterface sInstance;
+    @NonNull private final Context mContext;
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @NonNull protected final ExponentialBackoff mExponentialBackoff;
+    @NonNull private final Object mLock = new Object();
+    @NonNull private final SatelliteController mSatelliteController;
+    /**
+     * {@code true} to use the vendor satellite service and {@code false} to use the HAL.
+     */
+    private boolean mIsSatelliteServiceSupported;
+    @Nullable private ISatellite mSatelliteService;
+    @Nullable private SatelliteServiceConnection mSatelliteServiceConnection;
+    @NonNull private String mVendorSatellitePackageName = "";
+    private boolean mIsBound;
+    private boolean mIsBinding;
+
+    @NonNull private final RegistrantList mSatelliteProvisionStateChangedRegistrants =
+            new RegistrantList();
+    @NonNull private final RegistrantList mSatellitePositionInfoChangedRegistrants =
+            new RegistrantList();
+    @NonNull private final RegistrantList mDatagramTransferStateChangedRegistrants =
+            new RegistrantList();
+    @NonNull private final RegistrantList mSatelliteModemStateChangedRegistrants =
+            new RegistrantList();
+    @NonNull private final RegistrantList mPendingDatagramsRegistrants = new RegistrantList();
+    @NonNull private final RegistrantList mSatelliteDatagramsReceivedRegistrants =
+            new RegistrantList();
+
+    @NonNull private final ISatelliteListener mListener = new ISatelliteListener.Stub() {
+        @Override
+        public void onSatelliteProvisionStateChanged(boolean provisioned) {
+            mSatelliteProvisionStateChangedRegistrants.notifyResult(provisioned);
+        }
+
+        @Override
+        public void onSatelliteDatagramReceived(
+                android.telephony.satellite.stub.SatelliteDatagram datagram, int pendingCount) {
+            mSatelliteDatagramsReceivedRegistrants.notifyResult(new Pair<>(
+                    SatelliteServiceUtils.fromSatelliteDatagram(datagram), pendingCount));
+        }
+
+        @Override
+        public void onPendingDatagrams() {
+            mPendingDatagramsRegistrants.notifyResult(null);
+        }
+
+        @Override
+        public void onSatellitePositionChanged(
+                android.telephony.satellite.stub.PointingInfo pointingInfo) {
+            mSatellitePositionInfoChangedRegistrants.notifyResult(
+                    SatelliteServiceUtils.fromPointingInfo(pointingInfo));
+        }
+
+        @Override
+        public void onSatelliteModemStateChanged(int state) {
+            mSatelliteModemStateChangedRegistrants.notifyResult(
+                    SatelliteServiceUtils.fromSatelliteModemState(state));
+            int datagramTransferState = SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN;
+            switch (state) {
+                case SatelliteManager.SATELLITE_MODEM_STATE_IDLE:
+                    datagramTransferState = SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE;
+                    break;
+                case SatelliteManager.SATELLITE_MODEM_STATE_LISTENING:
+                    datagramTransferState =
+                            SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING;
+                    break;
+                case SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING:
+                    datagramTransferState =
+                            SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING;
+                    break;
+                case SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_RETRYING:
+                    // keep previous state as this could be retrying sending or receiving
+                    break;
+            }
+            mDatagramTransferStateChangedRegistrants.notifyResult(datagramTransferState);
+        }
+    };
+
+    /**
+     * @return The singleton instance of SatelliteModemInterface.
+     */
+    public static SatelliteModemInterface getInstance() {
+        if (sInstance == null) {
+            loge("SatelliteModemInterface was not yet initialized.");
+        }
+        return sInstance;
+    }
+
+    /**
+     * Create the SatelliteModemInterface singleton instance.
+     * @param context The Context to use to create the SatelliteModemInterface.
+     * @param satelliteController The singleton instance of SatelliteController.
+     * @return The singleton instance of SatelliteModemInterface.
+     */
+    public static SatelliteModemInterface make(@NonNull Context context,
+            SatelliteController satelliteController) {
+        if (sInstance == null) {
+            sInstance = new SatelliteModemInterface(
+                    context, satelliteController, Looper.getMainLooper());
+        }
+        return sInstance;
+    }
+
+    /**
+     * Create a SatelliteModemInterface to manage connections to the SatelliteService.
+     *
+     * @param context The Context for the SatelliteModemInterface.
+     * @param looper The Looper to run binding retry on.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected SatelliteModemInterface(@NonNull Context context,
+            SatelliteController satelliteController, @NonNull Looper looper) {
+        mContext = context;
+        mIsSatelliteServiceSupported = getSatelliteServiceSupport();
+        mSatelliteController = satelliteController;
+        mExponentialBackoff = new ExponentialBackoff(REBIND_INITIAL_DELAY, REBIND_MAXIMUM_DELAY,
+                REBIND_MULTIPLIER, looper, () -> {
+            synchronized (mLock) {
+                if ((mIsBound && mSatelliteService != null) || mIsBinding) {
+                    return;
+                }
+            }
+            if (mSatelliteServiceConnection != null) {
+                synchronized (mLock) {
+                    mIsBound = false;
+                    mIsBinding = false;
+                }
+                unbindService();
+            }
+            bindService();
+        });
+        mExponentialBackoff.start();
+        logd("Created SatelliteModemInterface. Attempting to bind to SatelliteService.");
+        bindService();
+    }
+
+    /**
+     * Get the SatelliteService interface, if it exists.
+     *
+     * @return The bound ISatellite, or {@code null} if it is not yet connected.
+     */
+    @Nullable public ISatellite getService() {
+        return mSatelliteService;
+    }
+
+    @NonNull private String getSatellitePackageName() {
+        if (!TextUtils.isEmpty(mVendorSatellitePackageName)) {
+            return mVendorSatellitePackageName;
+        }
+        return TextUtils.emptyIfNull(mContext.getResources().getString(
+                R.string.config_satellite_service_package));
+    }
+
+    private boolean getSatelliteServiceSupport() {
+        return !TextUtils.isEmpty(getSatellitePackageName());
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected void bindService() {
+        synchronized (mLock) {
+            if (mIsBinding || mIsBound) return;
+            mIsBinding = true;
+        }
+        String packageName = getSatellitePackageName();
+        if (TextUtils.isEmpty(packageName)) {
+            loge("Unable to bind to the satellite service because the package is undefined.");
+            // Since the package name comes from static device configs, stop retry because
+            // rebind will continue to fail without a valid package name.
+            synchronized (mLock) {
+                mIsBinding = false;
+            }
+            mExponentialBackoff.stop();
+            return;
+        }
+        Intent intent = new Intent(SatelliteService.SERVICE_INTERFACE);
+        intent.setPackage(packageName);
+
+        mSatelliteServiceConnection = new SatelliteServiceConnection();
+        logd("Binding to " + packageName);
+        try {
+            boolean success = mContext.bindService(
+                    intent, mSatelliteServiceConnection, Context.BIND_AUTO_CREATE);
+            if (success) {
+                logd("Successfully bound to the satellite service.");
+            } else {
+                synchronized (mLock) {
+                    mIsBinding = false;
+                }
+                mExponentialBackoff.notifyFailed();
+                loge("Error binding to the satellite service. Retrying in "
+                        + mExponentialBackoff.getCurrentDelay() + " ms.");
+            }
+        } catch (Exception e) {
+            synchronized (mLock) {
+                mIsBinding = false;
+            }
+            mExponentialBackoff.notifyFailed();
+            loge("Exception binding to the satellite service. Retrying in "
+                    + mExponentialBackoff.getCurrentDelay() + " ms. Exception: " + e);
+        }
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected void unbindService() {
+        disconnectSatelliteService();
+        mContext.unbindService(mSatelliteServiceConnection);
+        mSatelliteServiceConnection = null;
+    }
+
+    private void disconnectSatelliteService() {
+        // TODO: clean up any listeners and return failed for pending callbacks
+        mSatelliteService = null;
+    }
+
+    private class SatelliteServiceConnection implements ServiceConnection {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            logd("onServiceConnected: ComponentName=" + name);
+            synchronized (mLock) {
+                mIsBound = true;
+                mIsBinding = false;
+            }
+            mSatelliteService = ISatellite.Stub.asInterface(service);
+            mExponentialBackoff.stop();
+            try {
+                mSatelliteService.setSatelliteListener(mListener);
+            } catch (RemoteException e) {
+                // TODO: Retry setSatelliteListener
+                logd("setSatelliteListener: RemoteException " + e);
+            }
+            mSatelliteController.onSatelliteServiceConnected();
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            loge("onServiceDisconnected: Waiting for reconnect.");
+            synchronized (mLock) {
+                mIsBinding = false;
+            }
+            // Since we are still technically bound, clear the service and wait for reconnect.
+            disconnectSatelliteService();
+        }
+
+        @Override
+        public void onBindingDied(ComponentName name) {
+            loge("onBindingDied: Unbinding and rebinding service.");
+            synchronized (mLock) {
+                mIsBound = false;
+                mIsBinding = false;
+            }
+            unbindService();
+            mExponentialBackoff.start();
+        }
+    }
+
+    /**
+     * Registers for the satellite provision state changed.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForSatelliteProvisionStateChanged(
+            @NonNull Handler h, int what, @Nullable Object obj) {
+        mSatelliteProvisionStateChangedRegistrants.add(h, what, obj);
+    }
+
+    /**
+     * Unregisters for the satellite provision state changed.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForSatelliteProvisionStateChanged(@NonNull Handler h) {
+        mSatelliteProvisionStateChangedRegistrants.remove(h);
+    }
+
+    /**
+     * Registers for satellite position info changed from satellite modem.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForSatellitePositionInfoChanged(
+            @NonNull Handler h, int what, @Nullable Object obj) {
+        mSatellitePositionInfoChangedRegistrants.add(h, what, obj);
+    }
+
+    /**
+     * Unregisters for satellite position info changed from satellite modem.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForSatellitePositionInfoChanged(@NonNull Handler h) {
+        mSatellitePositionInfoChangedRegistrants.remove(h);
+    }
+
+    /**
+     * Registers for datagram transfer state changed.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForDatagramTransferStateChanged(
+            @NonNull Handler h, int what, @Nullable Object obj) {
+        mDatagramTransferStateChangedRegistrants.add(h, what, obj);
+    }
+
+    /**
+     * Unregisters for datagram transfer state changed.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForDatagramTransferStateChanged(@NonNull Handler h) {
+        mDatagramTransferStateChangedRegistrants.remove(h);
+    }
+
+    /**
+     * Registers for modem state changed from satellite modem.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForSatelliteModemStateChanged(
+            @NonNull Handler h, int what, @Nullable Object obj) {
+        mSatelliteModemStateChangedRegistrants.add(h, what, obj);
+    }
+
+    /**
+     * Unregisters for modem state changed from satellite modem.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForSatelliteModemStateChanged(@NonNull Handler h) {
+        mSatelliteModemStateChangedRegistrants.remove(h);
+    }
+
+    /**
+     * Registers for pending datagrams indication from satellite modem.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForPendingDatagrams(@NonNull Handler h, int what, @Nullable Object obj) {
+        mPendingDatagramsRegistrants.add(h, what, obj);
+    }
+
+    /**
+     * Unregisters for pending datagrams indication from satellite modem.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForPendingDatagrams(@NonNull Handler h) {
+        mPendingDatagramsRegistrants.remove(h);
+    }
+
+    /**
+     * Registers for new datagrams received from satellite modem.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForSatelliteDatagramsReceived(
+            @NonNull Handler h, int what, @Nullable Object obj) {
+        mSatelliteDatagramsReceivedRegistrants.add(h, what, obj);
+    }
+
+    /**
+     * Unregisters for new datagrams received from satellite modem.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForSatelliteDatagramsReceived(@NonNull Handler h) {
+        mSatelliteDatagramsReceivedRegistrants.remove(h);
+    }
+
+    /**
+     * Request to enable or disable the satellite service listening mode.
+     * Listening mode allows the satellite service to listen for incoming pages.
+     *
+     * @param enable True to enable satellite listening mode and false to disable.
+     * @param timeout How long the satellite modem should wait for the next incoming page before
+     *                disabling listening mode.
+     * @param message The Message to send to result of the operation to.
+     */
+    public void requestSatelliteListeningEnabled(boolean enable, int timeout,
+            @Nullable Message message) {
+        if (mSatelliteService != null) {
+            try {
+                mSatelliteService.requestSatelliteListeningEnabled(enable, timeout,
+                        new IIntegerConsumer.Stub() {
+                            @Override
+                            public void accept(int result) {
+                                int error = SatelliteServiceUtils.fromSatelliteError(result);
+                                logd("requestSatelliteListeningEnabled: " + error);
+                                Binder.withCleanCallingIdentity(() -> {
+                                    if (message != null) {
+                                        sendMessageWithResult(message, null, error);
+                                    }
+                                });
+                            }
+                        });
+            } catch (RemoteException e) {
+                loge("requestSatelliteListeningEnabled: RemoteException " + e);
+                if (message != null) {
+                    sendMessageWithResult(
+                            message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+                }
+            }
+        } else {
+            loge("requestSatelliteListeningEnabled: Satellite service is unavailable.");
+            if (message != null) {
+                sendMessageWithResult(message, null,
+                        SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+            }
+        }
+    }
+
+    /**
+     * Allow cellular modem scanning while satellite mode is on.
+     * @param enabled  {@code true} to enable cellular modem while satellite mode is on
+     * and {@code false} to disable
+     * @param message The Message to send to result of the operation to.
+     */
+    public void enableCellularModemWhileSatelliteModeIsOn(boolean enabled,
+            @Nullable Message message) {
+        if (mSatelliteService != null) {
+            try {
+                mSatelliteService.enableCellularModemWhileSatelliteModeIsOn(enabled,
+                        new IIntegerConsumer.Stub() {
+                            @Override
+                            public void accept(int result) {
+                                int error = SatelliteServiceUtils.fromSatelliteError(result);
+                                logd("enableCellularModemWhileSatelliteModeIsOn: " + error);
+                                Binder.withCleanCallingIdentity(() -> {
+                                        if (message != null) {
+                                            sendMessageWithResult(message, null, error);
+                                        }
+                                });
+                            }
+                        });
+            } catch (RemoteException e) {
+                loge("enableCellularModemWhileSatelliteModeIsOn: RemoteException " + e);
+                if (message != null) {
+                    sendMessageWithResult(
+                            message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+                }
+            }
+        } else {
+            loge("enableCellularModemWhileSatelliteModeIsOn: Satellite service is unavailable.");
+            if (message != null) {
+                sendMessageWithResult(message, null,
+                        SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+            }
+        }
+    }
+    /**
+     * Request to enable or disable the satellite modem and demo mode. If the satellite modem
+     * is enabled, this may also disable the cellular modem, and if the satellite modem is disabled,
+     * this may also re-enable the cellular modem.
+     *
+     * @param enableSatellite True to enable the satellite modem and false to disable.
+     * @param enableDemoMode True to enable demo mode and false to disable.
+     * @param message The Message to send to result of the operation to.
+     */
+    public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
+            @NonNull Message message) {
+        if (mSatelliteService != null) {
+            try {
+                mSatelliteService.requestSatelliteEnabled(enableSatellite, enableDemoMode,
+                        new IIntegerConsumer.Stub() {
+                    @Override
+                    public void accept(int result) {
+                        int error = SatelliteServiceUtils.fromSatelliteError(result);
+                        logd("setSatelliteEnabled: " + error);
+                        Binder.withCleanCallingIdentity(() ->
+                                sendMessageWithResult(message, null, error));
+                    }
+                });
+            } catch (RemoteException e) {
+                loge("setSatelliteEnabled: RemoteException " + e);
+                sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+            }
+        } else {
+            loge("setSatelliteEnabled: Satellite service is unavailable.");
+            sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+        }
+    }
+
+    /**
+     * Request to get whether the satellite modem is enabled.
+     *
+     * @param message The Message to send to result of the operation to.
+     */
+    public void requestIsSatelliteEnabled(@NonNull Message message) {
+        if (mSatelliteService != null) {
+            try {
+                mSatelliteService.requestIsSatelliteEnabled(new IIntegerConsumer.Stub() {
+                    @Override
+                    public void accept(int result) {
+                        int error = SatelliteServiceUtils.fromSatelliteError(result);
+                        logd("requestIsSatelliteEnabled: " + error);
+                        Binder.withCleanCallingIdentity(() ->
+                                sendMessageWithResult(message, null, error));
+                    }
+                }, new IBooleanConsumer.Stub() {
+                    @Override
+                    public void accept(boolean result) {
+                        // Convert for compatibility with SatelliteResponse
+                        // TODO: This should just report result instead.
+                        int[] enabled = new int[] {result ? 1 : 0};
+                        logd("requestIsSatelliteEnabled: " + Arrays.toString(enabled));
+                        Binder.withCleanCallingIdentity(() -> sendMessageWithResult(
+                                message, enabled, SatelliteManager.SATELLITE_ERROR_NONE));
+                    }
+                });
+            } catch (RemoteException e) {
+                loge("requestIsSatelliteEnabled: RemoteException " + e);
+                sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+            }
+        } else {
+            loge("requestIsSatelliteEnabled: Satellite service is unavailable.");
+            sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+        }
+    }
+
+    /**
+     * Request to get whether the satellite service is supported on the device.
+     *
+     * @param message The Message to send to result of the operation to.
+     */
+    public void requestIsSatelliteSupported(@NonNull Message message) {
+        if (mSatelliteService != null) {
+            try {
+                mSatelliteService.requestIsSatelliteSupported(new IIntegerConsumer.Stub() {
+                    @Override
+                    public void accept(int result) {
+                        int error = SatelliteServiceUtils.fromSatelliteError(result);
+                        logd("requestIsSatelliteSupported: " + error);
+                        Binder.withCleanCallingIdentity(() ->
+                                sendMessageWithResult(message, null, error));
+                    }
+                }, new IBooleanConsumer.Stub() {
+                    @Override
+                    public void accept(boolean result) {
+                        logd("requestIsSatelliteSupported: " + result);
+                        Binder.withCleanCallingIdentity(() -> sendMessageWithResult(
+                                message, result, SatelliteManager.SATELLITE_ERROR_NONE));
+                    }
+                });
+            } catch (RemoteException e) {
+                loge("requestIsSatelliteSupported: RemoteException " + e);
+                sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+            }
+        } else {
+            loge("requestIsSatelliteSupported: Satellite service is unavailable.");
+            sendMessageWithResult(
+                    message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+        }
+    }
+
+    /**
+     * Request to get the SatelliteCapabilities of the satellite service.
+     *
+     * @param message The Message to send to result of the operation to.
+     */
+    public void requestSatelliteCapabilities(@NonNull Message message) {
+        if (mSatelliteService != null) {
+            try {
+                mSatelliteService.requestSatelliteCapabilities(new IIntegerConsumer.Stub() {
+                    @Override
+                    public void accept(int result) {
+                        int error = SatelliteServiceUtils.fromSatelliteError(result);
+                        logd("requestSatelliteCapabilities: " + error);
+                        Binder.withCleanCallingIdentity(() ->
+                                sendMessageWithResult(message, null, error));
+                    }
+                }, new ISatelliteCapabilitiesConsumer.Stub() {
+                    @Override
+                    public void accept(android.telephony.satellite.stub.SatelliteCapabilities
+                            result) {
+                        SatelliteCapabilities capabilities =
+                                SatelliteServiceUtils.fromSatelliteCapabilities(result);
+                        logd("requestSatelliteCapabilities: " + capabilities);
+                        Binder.withCleanCallingIdentity(() -> sendMessageWithResult(
+                                message, capabilities, SatelliteManager.SATELLITE_ERROR_NONE));
+                    }
+                });
+            } catch (RemoteException e) {
+                loge("requestSatelliteCapabilities: RemoteException " + e);
+                sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+            }
+        } else {
+            loge("requestSatelliteCapabilities: Satellite service is unavailable.");
+            sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+        }
+    }
+
+    /**
+     * User started pointing to the satellite.
+     * The satellite service should report the satellite pointing info via
+     * ISatelliteListener#onSatellitePositionChanged as the user device/satellite moves.
+     *
+     * @param message The Message to send to result of the operation to.
+     */
+    public void startSendingSatellitePointingInfo(@NonNull Message message) {
+        if (mSatelliteService != null) {
+            try {
+                mSatelliteService.startSendingSatellitePointingInfo(new IIntegerConsumer.Stub() {
+                    @Override
+                    public void accept(int result) {
+                        int error = SatelliteServiceUtils.fromSatelliteError(result);
+                        logd("startSendingSatellitePointingInfo: " + error);
+                        Binder.withCleanCallingIdentity(() ->
+                                sendMessageWithResult(message, null, error));
+                    }
+                });
+            } catch (RemoteException e) {
+                loge("startSendingSatellitePointingInfo: RemoteException " + e);
+                sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+            }
+        } else {
+            loge("startSendingSatellitePointingInfo: Satellite service is unavailable.");
+            sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+        }
+    }
+
+    /**
+     * User stopped pointing to the satellite.
+     * The satellite service should stop reporting satellite pointing info to the framework.
+     *
+     * @param message The Message to send to result of the operation to.
+     */
+    public void stopSendingSatellitePointingInfo(@NonNull Message message) {
+        if (mSatelliteService != null) {
+            try {
+                mSatelliteService.stopSendingSatellitePointingInfo(new IIntegerConsumer.Stub() {
+                    @Override
+                    public void accept(int result) {
+                        int error = SatelliteServiceUtils.fromSatelliteError(result);
+                        logd("stopSendingSatellitePointingInfo: " + error);
+                        Binder.withCleanCallingIdentity(() ->
+                                sendMessageWithResult(message, null, error));
+                    }
+                });
+            } catch (RemoteException e) {
+                loge("stopSendingSatellitePointingInfo: RemoteException " + e);
+                sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+            }
+        } else {
+            loge("stopSendingSatellitePointingInfo: Satellite service is unavailable.");
+            sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+        }
+    }
+
+    /**
+     * Provision the device with a satellite provider.
+     * This is needed if the provider allows dynamic registration.
+     * Once provisioned, ISatelliteListener#onSatelliteProvisionStateChanged should report true.
+     *
+     * @param token The token to be used as a unique identifier for provisioning with satellite
+     *              gateway.
+     * @param provisionData Data from the provisioning app that can be used by provisioning server
+     * @param message The Message to send to result of the operation to.
+     */
+    public void provisionSatelliteService(@NonNull String token, @NonNull byte[] provisionData,
+            @NonNull Message message) {
+        if (mSatelliteService != null) {
+            try {
+                mSatelliteService.provisionSatelliteService(token, provisionData,
+                        new IIntegerConsumer.Stub() {
+                            @Override
+                            public void accept(int result) {
+                                int error = SatelliteServiceUtils.fromSatelliteError(result);
+                                logd("provisionSatelliteService: " + error);
+                                Binder.withCleanCallingIdentity(() ->
+                                        sendMessageWithResult(message, null, error));
+                            }
+                        });
+            } catch (RemoteException e) {
+                loge("provisionSatelliteService: RemoteException " + e);
+                sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+            }
+        } else {
+            loge("provisionSatelliteService: Satellite service is unavailable.");
+            sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+        }
+    }
+
+    /**
+     * Deprovision the device with the satellite provider.
+     * This is needed if the provider allows dynamic registration.
+     * Once deprovisioned, ISatelliteListener#onSatelliteProvisionStateChanged should report false.
+     *
+     * @param token The token of the device/subscription to be deprovisioned.
+     * @param message The Message to send to result of the operation to.
+     */
+    public void deprovisionSatelliteService(@NonNull String token, @NonNull Message message) {
+        if (mSatelliteService != null) {
+            try {
+                mSatelliteService.deprovisionSatelliteService(token, new IIntegerConsumer.Stub() {
+                    @Override
+                    public void accept(int result) {
+                        int error = SatelliteServiceUtils.fromSatelliteError(result);
+                        logd("deprovisionSatelliteService: " + error);
+                        Binder.withCleanCallingIdentity(() ->
+                                sendMessageWithResult(message, null, error));
+                    }
+                });
+            } catch (RemoteException e) {
+                loge("deprovisionSatelliteService: RemoteException " + e);
+                sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+            }
+        } else {
+            loge("deprovisionSatelliteService: Satellite service is unavailable.");
+            sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+        }
+    }
+
+    /**
+     * Request to get whether this device is provisioned with a satellite provider.
+     *
+     * @param message The Message to send to result of the operation to.
+     */
+    public void requestIsSatelliteProvisioned(@NonNull Message message) {
+        if (mSatelliteService != null) {
+            try {
+                mSatelliteService.requestIsSatelliteProvisioned(new IIntegerConsumer.Stub() {
+                    @Override
+                    public void accept(int result) {
+                        int error = SatelliteServiceUtils.fromSatelliteError(result);
+                        logd("requestIsSatelliteProvisioned: " + error);
+                        Binder.withCleanCallingIdentity(() ->
+                                sendMessageWithResult(message, null, error));
+                    }
+                }, new IBooleanConsumer.Stub() {
+                    @Override
+                    public void accept(boolean result) {
+                        // Convert for compatibility with SatelliteResponse
+                        // TODO: This should just report result instead.
+                        int[] provisioned = new int[] {result ? 1 : 0};
+                        logd("requestIsSatelliteProvisioned: " + Arrays.toString(provisioned));
+                        Binder.withCleanCallingIdentity(() -> sendMessageWithResult(
+                                message, provisioned, SatelliteManager.SATELLITE_ERROR_NONE));
+                    }
+                });
+            } catch (RemoteException e) {
+                loge("requestIsSatelliteProvisioned: RemoteException " + e);
+                sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+            }
+        } else {
+            loge("requestIsSatelliteProvisioned: Satellite service is unavailable.");
+            sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+        }
+    }
+
+    /**
+     * Poll the pending datagrams to be received over satellite.
+     * The satellite service should check if there are any pending datagrams to be received over
+     * satellite and report them via ISatelliteListener#onSatelliteDatagramsReceived.
+     *
+     * @param message The Message to send to result of the operation to.
+     */
+    public void pollPendingSatelliteDatagrams(@NonNull Message message) {
+        if (mSatelliteService != null) {
+            try {
+                mSatelliteService.pollPendingSatelliteDatagrams(new IIntegerConsumer.Stub() {
+                    @Override
+                    public void accept(int result) {
+                        int error = SatelliteServiceUtils.fromSatelliteError(result);
+                        logd("pollPendingSatelliteDatagrams: " + error);
+                        Binder.withCleanCallingIdentity(() ->
+                                sendMessageWithResult(message, null, error));
+                    }
+                });
+            } catch (RemoteException e) {
+                loge("pollPendingSatelliteDatagrams: RemoteException " + e);
+                sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+            }
+        } else {
+            loge("pollPendingSatelliteDatagrams: Satellite service is unavailable.");
+            sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+        }
+    }
+
+    /**
+     * Send datagram over satellite.
+     *
+     * @param datagram Datagram to send in byte format.
+     * @param isEmergency Whether this is an emergency datagram.
+     * @param needFullScreenPointingUI this is used to indicate pointingUI app to open in
+     *                                 full screen mode.
+     * @param message The Message to send to result of the operation to.
+     */
+    public void sendSatelliteDatagram(@NonNull SatelliteDatagram datagram, boolean isEmergency,
+            boolean needFullScreenPointingUI, @NonNull Message message) {
+        if (mSatelliteService != null) {
+            try {
+                mSatelliteService.sendSatelliteDatagram(
+                        SatelliteServiceUtils.toSatelliteDatagram(datagram), isEmergency,
+                        new IIntegerConsumer.Stub() {
+                            @Override
+                            public void accept(int result) {
+                                int error = SatelliteServiceUtils.fromSatelliteError(result);
+                                logd("sendSatelliteDatagram: " + error);
+                                Binder.withCleanCallingIdentity(() ->
+                                        sendMessageWithResult(message, null, error));
+                            }
+                        });
+            } catch (RemoteException e) {
+                loge("sendSatelliteDatagram: RemoteException " + e);
+                sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+            }
+        } else {
+            loge("sendSatelliteDatagram: Satellite service is unavailable.");
+            sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+        }
+    }
+
+    /**
+     * Request the current satellite modem state.
+     * The satellite service should report the current satellite modem state via
+     * ISatelliteListener#onSatelliteModemStateChanged.
+     *
+     * @param message The Message to send to result of the operation to.
+     */
+    public void requestSatelliteModemState(@NonNull Message message) {
+        if (mSatelliteService != null) {
+            try {
+                mSatelliteService.requestSatelliteModemState(new IIntegerConsumer.Stub() {
+                    @Override
+                    public void accept(int result) {
+                        int error = SatelliteServiceUtils.fromSatelliteError(result);
+                        logd("requestSatelliteModemState: " + error);
+                        Binder.withCleanCallingIdentity(() ->
+                                sendMessageWithResult(message, null, error));
+                    }
+                }, new IIntegerConsumer.Stub() {
+                    @Override
+                    public void accept(int result) {
+                        // Convert SatelliteModemState from service to frameworks definition.
+                        int modemState = SatelliteServiceUtils.fromSatelliteModemState(result);
+                        logd("requestSatelliteModemState: " + modemState);
+                        Binder.withCleanCallingIdentity(() -> sendMessageWithResult(
+                                message, modemState, SatelliteManager.SATELLITE_ERROR_NONE));
+                    }
+                });
+            } catch (RemoteException e) {
+                loge("requestSatelliteModemState: RemoteException " + e);
+                sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+            }
+        } else {
+            loge("requestSatelliteModemState: Satellite service is unavailable.");
+            sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+        }
+    }
+
+    /**
+     * Request to get whether satellite communication is allowed for the current location.
+     *
+     * @param message The Message to send to result of the operation to.
+     */
+    public void requestIsSatelliteCommunicationAllowedForCurrentLocation(@NonNull Message message) {
+        if (mSatelliteService != null) {
+            try {
+                mSatelliteService.requestIsSatelliteCommunicationAllowedForCurrentLocation(
+                        new IIntegerConsumer.Stub() {
+                            @Override
+                            public void accept(int result) {
+                                int error = SatelliteServiceUtils.fromSatelliteError(result);
+                                logd("requestIsSatelliteCommunicationAllowedForCurrentLocation: "
+                                        + error);
+                                Binder.withCleanCallingIdentity(() ->
+                                        sendMessageWithResult(message, null, error));
+                            }
+                        }, new IBooleanConsumer.Stub() {
+                            @Override
+                            public void accept(boolean result) {
+                                logd("requestIsSatelliteCommunicationAllowedForCurrentLocation: "
+                                        + result);
+                                Binder.withCleanCallingIdentity(() -> sendMessageWithResult(
+                                        message, result, SatelliteManager.SATELLITE_ERROR_NONE));
+                            }
+                        });
+            } catch (RemoteException e) {
+                loge("requestIsSatelliteCommunicationAllowedForCurrentLocation: RemoteException "
+                        + e);
+                sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+            }
+        } else {
+            loge("requestIsSatelliteCommunicationAllowedForCurrentLocation: "
+                    + "Satellite service is unavailable.");
+            sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+        }
+    }
+
+    /**
+     * Request to get the time after which the satellite will be visible. This is an int
+     * representing the duration in seconds after which the satellite will be visible.
+     * This will return 0 if the satellite is currently visible.
+     *
+     * @param message The Message to send to result of the operation to.
+     */
+    public void requestTimeForNextSatelliteVisibility(@NonNull Message message) {
+        if (mSatelliteService != null) {
+            try {
+                mSatelliteService.requestTimeForNextSatelliteVisibility(
+                        new IIntegerConsumer.Stub() {
+                            @Override
+                            public void accept(int result) {
+                                int error = SatelliteServiceUtils.fromSatelliteError(result);
+                                logd("requestTimeForNextSatelliteVisibility: " + error);
+                                Binder.withCleanCallingIdentity(() ->
+                                        sendMessageWithResult(message, null, error));
+                            }
+                        }, new IIntegerConsumer.Stub() {
+                            @Override
+                            public void accept(int result) {
+                                // Convert for compatibility with SatelliteResponse
+                                // TODO: This should just report result instead.
+                                int[] time = new int[] {result};
+                                logd("requestTimeForNextSatelliteVisibility: "
+                                        + Arrays.toString(time));
+                                Binder.withCleanCallingIdentity(() -> sendMessageWithResult(
+                                        message, time, SatelliteManager.SATELLITE_ERROR_NONE));
+                            }
+                        });
+            } catch (RemoteException e) {
+                loge("requestTimeForNextSatelliteVisibility: RemoteException " + e);
+                sendMessageWithResult(message, null, SatelliteManager.SATELLITE_SERVICE_ERROR);
+            }
+        } else {
+            loge("requestTimeForNextSatelliteVisibility: Satellite service is unavailable.");
+            sendMessageWithResult(message, null, SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE);
+        }
+    }
+
+    public boolean isSatelliteServiceSupported() {
+        return mIsSatelliteServiceSupported;
+    }
+
+    /**
+     * This API can be used by only CTS to update satellite vendor service package name.
+     *
+     * @param servicePackageName The package name of the satellite vendor service.
+     * @return {@code true} if the satellite vendor service is set successfully,
+     * {@code false} otherwise.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public boolean setSatelliteServicePackageName(@Nullable String servicePackageName) {
+        if (!shouldAllowModifyingSatelliteServicePackageName()) {
+            loge("setSatelliteServicePackageName: modifying satellite service package name "
+                    + "is not allowed");
+            return false;
+        }
+
+        logd("setSatelliteServicePackageName: config_satellite_service_package is "
+                + "updated, new packageName=" + servicePackageName);
+        mExponentialBackoff.stop();
+        if (mSatelliteServiceConnection != null) {
+            synchronized (mLock) {
+                mIsBound = false;
+                mIsBinding = false;
+            }
+            unbindService();
+        }
+
+        if (servicePackageName == null || servicePackageName.equals("null")) {
+            mVendorSatellitePackageName = "";
+        } else {
+            mVendorSatellitePackageName = servicePackageName;
+        }
+        mIsSatelliteServiceSupported = getSatelliteServiceSupport();
+        bindService();
+        mExponentialBackoff.start();
+
+        return true;
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected static void sendMessageWithResult(@NonNull Message message, @Nullable Object result,
+            @SatelliteManager.SatelliteError int error) {
+        SatelliteException exception = error == SatelliteManager.SATELLITE_ERROR_NONE
+                ? null : new SatelliteException(error);
+        AsyncResult.forMessage(message, result, exception);
+        message.sendToTarget();
+    }
+
+    private boolean shouldAllowModifyingSatelliteServicePackageName() {
+        return (DEBUG || SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false));
+    }
+
+    private static void logd(@NonNull String log) {
+        Rlog.d(TAG, log);
+    }
+
+    private static void loge(@NonNull String log) {
+        Rlog.e(TAG, log);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java b/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java
new file mode 100644
index 0000000..25657b3
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java
@@ -0,0 +1,410 @@
+/*
+ * 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.satellite;
+
+import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_COMMUNICATION_ALLOWED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_ERROR_NONE;
+
+import android.annotation.NonNull;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ResultReceiver;
+import android.provider.DeviceConfig;
+import android.telecom.Call;
+import android.telecom.Connection;
+import android.telephony.Rlog;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsRegistrationAttributes;
+import android.telephony.ims.RegistrationManager;
+import android.telephony.satellite.ISatelliteProvisionStateCallback;
+import android.util.Pair;
+
+import com.android.ims.ImsException;
+import com.android.ims.ImsManager;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.metrics.SatelliteStats;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+/**
+ * This module is responsible for monitoring the cellular service state and IMS registration state
+ * during an emergency call and notify Dialer when Telephony is not able to find any network and
+ * the call likely will not get connected so that Dialer will prompt the user if they would like to
+ * switch to satellite messaging.
+ */
+public class SatelliteSOSMessageRecommender extends Handler {
+    private static final String TAG = "SatelliteSOSMessageRecommender";
+
+    /**
+     * Device config for the timeout duration in milliseconds to determine whether to recommend
+     * Dialer to show the SOS button to users.
+     * <p>
+     * The timer is started when there is an ongoing emergency call, and the IMS is not registered,
+     * and cellular service is not available. When the timer expires, SatelliteSOSMessageRecommender
+     * will send the event EVENT_DISPLAY_SOS_MESSAGE to Dialer, which will then prompt user to
+     * switch to using satellite SOS messaging.
+     */
+    public static final String EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS =
+            "emergency_call_to_sos_msg_hysteresis_timeout_millis";
+    /**
+     * The default value of {@link #EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS} when it is
+     * not provided in the device config.
+     */
+    public static final long DEFAULT_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS = 20000;
+
+    private static final int EVENT_EMERGENCY_CALL_STARTED = 1;
+    protected static final int EVENT_CELLULAR_SERVICE_STATE_CHANGED = 2;
+    private static final int EVENT_IMS_REGISTRATION_STATE_CHANGED = 3;
+    protected static final int EVENT_TIME_OUT = 4;
+    private static final int EVENT_SATELLITE_PROVISIONED_STATE_CHANGED = 5;
+    private static final int EVENT_EMERGENCY_CALL_CONNECTION_STATE_CHANGED = 6;
+
+    @NonNull
+    private final SatelliteController mSatelliteController;
+    private ImsManager mImsManager;
+
+    private Connection mEmergencyConnection = null;
+    /* The phone used for emergency call */
+    private Phone mPhone = null;
+    private final ISatelliteProvisionStateCallback mISatelliteProvisionStateCallback;
+    @ServiceState.RegState
+    private AtomicInteger mCellularServiceState = new AtomicInteger();
+    private AtomicBoolean mIsImsRegistered = new AtomicBoolean();
+    private AtomicBoolean mIsSatelliteAllowedInCurrentLocation = new AtomicBoolean();
+    private final ResultReceiver mReceiverForRequestIsSatelliteAllowedForCurrentLocation;
+    private final long mTimeoutMillis;
+    protected int mCountOfTimerStarted = 0;
+
+    private RegistrationManager.RegistrationCallback mImsRegistrationCallback =
+            new RegistrationManager.RegistrationCallback() {
+                @Override
+                public void onRegistered(ImsRegistrationAttributes attributes) {
+                    sendMessage(obtainMessage(EVENT_IMS_REGISTRATION_STATE_CHANGED, true));
+                }
+
+                @Override
+                public void onUnregistered(ImsReasonInfo info) {
+                    sendMessage(obtainMessage(EVENT_IMS_REGISTRATION_STATE_CHANGED, false));
+                }
+            };
+
+    /**
+     * Create an instance of SatelliteSOSMessageRecommender.
+     *
+     * @param looper The looper used with the handler of this class.
+     */
+    public SatelliteSOSMessageRecommender(@NonNull Looper looper) {
+        this(looper, SatelliteController.getInstance(), null,
+                getEmergencyCallToSosMsgHysteresisTimeoutMillis());
+    }
+
+    /**
+     * Create an instance of SatelliteSOSMessageRecommender. This constructor should be used in
+     * only unit tests.
+     *
+     * @param looper The looper used with the handler of this class.
+     * @param satelliteController The SatelliteController singleton instance.
+     * @param imsManager The ImsManager instance associated with the phone, which is used for making
+     *                   the emergency call. This argument is not null only in unit tests.
+     * @param timeoutMillis The timeout duration of the timer.
+     */
+    @VisibleForTesting
+    protected SatelliteSOSMessageRecommender(@NonNull Looper looper,
+            @NonNull SatelliteController satelliteController, ImsManager imsManager,
+            long timeoutMillis) {
+        super(looper);
+        mSatelliteController = satelliteController;
+        mImsManager = imsManager;
+        mTimeoutMillis = timeoutMillis;
+        mISatelliteProvisionStateCallback = new ISatelliteProvisionStateCallback.Stub() {
+            @Override
+            public void onSatelliteProvisionStateChanged(boolean provisioned) {
+                logd("onSatelliteProvisionStateChanged: provisioned=" + provisioned);
+                sendMessage(obtainMessage(EVENT_SATELLITE_PROVISIONED_STATE_CHANGED, provisioned));
+            }
+        };
+        mReceiverForRequestIsSatelliteAllowedForCurrentLocation = new ResultReceiver(this) {
+            @Override
+            protected void onReceiveResult(int resultCode, Bundle resultData) {
+                if (resultCode == SATELLITE_ERROR_NONE) {
+                    if (resultData.containsKey(KEY_SATELLITE_COMMUNICATION_ALLOWED)) {
+                        boolean isSatelliteCommunicationAllowed =
+                                resultData.getBoolean(KEY_SATELLITE_COMMUNICATION_ALLOWED);
+                        mIsSatelliteAllowedInCurrentLocation.set(isSatelliteCommunicationAllowed);
+                        if (!isSatelliteCommunicationAllowed) {
+                            logd("Satellite is not allowed for current location.");
+                            cleanUpResources();
+                        }
+                    } else {
+                        loge("KEY_SATELLITE_COMMUNICATION_ALLOWED does not exist.");
+                        mIsSatelliteAllowedInCurrentLocation.set(false);
+                        cleanUpResources();
+                    }
+                } else {
+                    loge("requestIsSatelliteCommunicationAllowedForCurrentLocation() resultCode="
+                            + resultCode);
+                    mIsSatelliteAllowedInCurrentLocation.set(false);
+                    cleanUpResources();
+                }
+            }
+        };
+    }
+
+    @Override
+    public void handleMessage(@NonNull Message msg) {
+        switch (msg.what) {
+            case EVENT_EMERGENCY_CALL_STARTED:
+                handleEmergencyCallStartedEvent((Pair<Connection, Phone>) msg.obj);
+                break;
+            case EVENT_TIME_OUT:
+                handleTimeoutEvent();
+                break;
+            case EVENT_SATELLITE_PROVISIONED_STATE_CHANGED:
+                handleSatelliteProvisionStateChangedEvent((boolean) msg.obj);
+                break;
+            case EVENT_EMERGENCY_CALL_CONNECTION_STATE_CHANGED:
+                handleEmergencyCallConnectionStateChangedEvent((Pair<String, Integer>) msg.obj);
+                break;
+            case EVENT_IMS_REGISTRATION_STATE_CHANGED:
+                handleImsRegistrationStateChangedEvent((boolean) msg.obj);
+                break;
+            case EVENT_CELLULAR_SERVICE_STATE_CHANGED:
+                AsyncResult ar = (AsyncResult) msg.obj;
+                handleCellularServiceStateChangedEvent((ServiceState) ar.result);
+                break;
+            default:
+                logd("handleMessage: unexpected message code: " + msg.what);
+                break;
+        }
+    }
+
+    /**
+     * Inform SatelliteSOSMessageRecommender that an emergency call has just started.
+     *
+     * @param connection The connection created by TelephonyConnectionService for the emergency
+     *                   call.
+     * @param phone The phone used for the emergency call.
+     */
+    public void onEmergencyCallStarted(@NonNull Connection connection, @NonNull Phone phone) {
+        if (!mSatelliteController.isSatelliteSupported()) {
+            logd("onEmergencyCallStarted: satellite is not supported");
+            return;
+        }
+        Pair<Connection, Phone> argument = new Pair<>(connection, phone);
+        sendMessage(obtainMessage(EVENT_EMERGENCY_CALL_STARTED, argument));
+    }
+
+    /**
+     * Inform SatelliteSOSMessageRecommender that the state of the emergency call connection has
+     * changed.
+     *
+     * @param callId The ID of the emergency call.
+     * @param state The connection state of the emergency call.
+     */
+    public void onEmergencyCallConnectionStateChanged(
+            String callId, @Connection.ConnectionState int state) {
+        Pair<String, Integer> argument = new Pair<>(callId, state);
+        sendMessage(obtainMessage(EVENT_EMERGENCY_CALL_CONNECTION_STATE_CHANGED, argument));
+    }
+
+    private void handleEmergencyCallStartedEvent(@NonNull Pair<Connection, Phone> arg) {
+        mSatelliteController.requestIsSatelliteCommunicationAllowedForCurrentLocation(
+                SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                mReceiverForRequestIsSatelliteAllowedForCurrentLocation);
+        if (mPhone != null) {
+            logd("handleEmergencyCallStartedEvent: new emergency call started while there is "
+                    + " an ongoing call");
+            unregisterForInterestedStateChangedEvents(mPhone);
+        }
+        mPhone = arg.second;
+        mEmergencyConnection = arg.first;
+        mCellularServiceState.set(mPhone.getServiceState().getState());
+        mIsImsRegistered.set(mPhone.isImsRegistered());
+        handleStateChangedEventForHysteresisTimer();
+        registerForInterestedStateChangedEvents(mPhone);
+    }
+
+    private void handleSatelliteProvisionStateChangedEvent(boolean provisioned) {
+        if (!provisioned) {
+            cleanUpResources();
+        }
+    }
+
+    private void handleTimeoutEvent() {
+        boolean isDialerNotified = false;
+        if (!mIsImsRegistered.get() && !isCellularAvailable()
+                && mIsSatelliteAllowedInCurrentLocation.get()
+                && mSatelliteController.isSatelliteProvisioned()
+                && shouldTrackCall(mEmergencyConnection.getState())) {
+            logd("handleTimeoutEvent: Sending EVENT_DISPLAY_SOS_MESSAGE to Dialer...");
+            mEmergencyConnection.sendConnectionEvent(Call.EVENT_DISPLAY_SOS_MESSAGE, null);
+            isDialerNotified = true;
+        }
+        reportEsosRecommenderDecision(isDialerNotified);
+        cleanUpResources();
+    }
+
+    private void handleEmergencyCallConnectionStateChangedEvent(
+            @NonNull Pair<String, Integer> arg) {
+        if (mEmergencyConnection == null) {
+            // Either the call was not created or the timer already timed out.
+            return;
+        }
+
+        String callId = arg.first;
+        int state = arg.second;
+        if (!mEmergencyConnection.getTelecomCallId().equals(callId)) {
+            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.
+             */
+            cleanUpResources();
+            return;
+        }
+
+        if (!shouldTrackCall(state)) {
+            reportEsosRecommenderDecision(false);
+            cleanUpResources();
+        }
+    }
+
+    private void handleImsRegistrationStateChangedEvent(boolean registered) {
+        if (registered != mIsImsRegistered.get()) {
+            mIsImsRegistered.set(registered);
+            handleStateChangedEventForHysteresisTimer();
+        }
+    }
+
+    private void handleCellularServiceStateChangedEvent(@NonNull ServiceState serviceState) {
+        int state = serviceState.getState();
+        if (mCellularServiceState.get() != state) {
+            mCellularServiceState.set(state);
+            handleStateChangedEventForHysteresisTimer();
+        }
+    }
+
+    private void reportEsosRecommenderDecision(boolean isDialerNotified) {
+        SatelliteStats.getInstance().onSatelliteSosMessageRecommender(
+                new SatelliteStats.SatelliteSosMessageRecommenderParams.Builder()
+                        .setDisplaySosMessageSent(isDialerNotified)
+                        .setCountOfTimerStarted(mCountOfTimerStarted)
+                        .setImsRegistered(mIsImsRegistered.get())
+                        .setCellularServiceState(mCellularServiceState.get())
+                        .build());
+    }
+
+    private void cleanUpResources() {
+        stopTimer();
+        if (mPhone != null) {
+            unregisterForInterestedStateChangedEvents(mPhone);
+            mPhone = null;
+        }
+        mEmergencyConnection = null;
+        mCountOfTimerStarted = 0;
+    }
+
+    private void registerForInterestedStateChangedEvents(@NonNull Phone phone) {
+        mSatelliteController.registerForSatelliteProvisionStateChanged(
+                SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, mISatelliteProvisionStateCallback);
+        phone.registerForServiceStateChanged(this, EVENT_CELLULAR_SERVICE_STATE_CHANGED, null);
+        registerForImsRegistrationStateChanged(phone);
+    }
+
+    private void registerForImsRegistrationStateChanged(@NonNull Phone phone) {
+        ImsManager imsManager = (mImsManager != null) ? mImsManager : ImsManager.getInstance(
+                phone.getContext(), phone.getPhoneId());
+        try {
+            imsManager.addRegistrationCallback(mImsRegistrationCallback, this::post);
+        } catch (ImsException ex) {
+            loge("registerForImsRegistrationStateChanged: ex=" + ex);
+        }
+    }
+
+    private void unregisterForInterestedStateChangedEvents(@NonNull Phone phone) {
+        mSatelliteController.unregisterForSatelliteProvisionStateChanged(
+                SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, mISatelliteProvisionStateCallback);
+        phone.unregisterForServiceStateChanged(this);
+        unregisterForImsRegistrationStateChanged(phone);
+    }
+
+    private void unregisterForImsRegistrationStateChanged(@NonNull Phone phone) {
+        ImsManager imsManager = (mImsManager != null) ? mImsManager : ImsManager.getInstance(
+                phone.getContext(), phone.getPhoneId());
+        imsManager.removeRegistrationListener(mImsRegistrationCallback);
+    }
+
+    private boolean isCellularAvailable() {
+        return (mCellularServiceState.get() == ServiceState.STATE_IN_SERVICE
+                || mCellularServiceState.get() == ServiceState.STATE_EMERGENCY_ONLY);
+    }
+
+    private void handleStateChangedEventForHysteresisTimer() {
+        if (!mIsImsRegistered.get() && !isCellularAvailable()) {
+            startTimer();
+        } else {
+            stopTimer();
+        }
+    }
+
+    private void startTimer() {
+        if (hasMessages(EVENT_TIME_OUT)) {
+            return;
+        }
+        sendMessageDelayed(obtainMessage(EVENT_TIME_OUT), mTimeoutMillis);
+        mCountOfTimerStarted++;
+    }
+
+    private void stopTimer() {
+        removeMessages(EVENT_TIME_OUT);
+    }
+
+    private static long getEmergencyCallToSosMsgHysteresisTimeoutMillis() {
+        return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
+                EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS,
+                DEFAULT_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS);
+    }
+
+    private boolean shouldTrackCall(int connectionState) {
+        /**
+         * An active connection state means both parties are connected to the call and can actively
+         * communicate. A disconnected connection state means the emergency call has ended. In both
+         * cases, we don't need to track the call anymore.
+         */
+        return (connectionState != Connection.STATE_ACTIVE
+                && connectionState != Connection.STATE_DISCONNECTED);
+    }
+
+    private static void logd(@NonNull String log) {
+        Rlog.d(TAG, log);
+    }
+
+    private static void loge(@NonNull String log) {
+        Rlog.e(TAG, log);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java b/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java
new file mode 100644
index 0000000..f11ca66
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java
@@ -0,0 +1,285 @@
+/*
+ * 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.satellite;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.Binder;
+import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
+import android.telephony.satellite.AntennaPosition;
+import android.telephony.satellite.PointingInfo;
+import android.telephony.satellite.SatelliteCapabilities;
+import android.telephony.satellite.SatelliteDatagram;
+import android.telephony.satellite.SatelliteManager;
+import android.telephony.satellite.stub.NTRadioTechnology;
+import android.telephony.satellite.stub.SatelliteError;
+import android.telephony.satellite.stub.SatelliteModemState;
+
+import com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.RILUtils;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * Utils class for satellite service <-> framework conversions
+ */
+public class SatelliteServiceUtils {
+    private static final String TAG = "SatelliteServiceUtils";
+
+    /**
+     * Convert radio technology from service definition to framework definition.
+     * @param radioTechnology The NTRadioTechnology from the satellite service.
+     * @return The converted NTRadioTechnology for the framework.
+     */
+    @SatelliteManager.NTRadioTechnology
+    public static int fromSatelliteRadioTechnology(int radioTechnology) {
+        switch (radioTechnology) {
+            case NTRadioTechnology.NB_IOT_NTN:
+                return SatelliteManager.NT_RADIO_TECHNOLOGY_NB_IOT_NTN;
+            case NTRadioTechnology.NR_NTN:
+                return SatelliteManager.NT_RADIO_TECHNOLOGY_NR_NTN;
+            case NTRadioTechnology.EMTC_NTN:
+                return SatelliteManager.NT_RADIO_TECHNOLOGY_EMTC_NTN;
+            case NTRadioTechnology.PROPRIETARY:
+                return SatelliteManager.NT_RADIO_TECHNOLOGY_PROPRIETARY;
+            default:
+                loge("Received invalid radio technology: " + radioTechnology);
+                return SatelliteManager.NT_RADIO_TECHNOLOGY_UNKNOWN;
+        }
+    }
+
+    /**
+     * Convert satellite error from service definition to framework definition.
+     * @param error The SatelliteError from the satellite service.
+     * @return The converted SatelliteError for the framework.
+     */
+    @SatelliteManager.SatelliteError public static int fromSatelliteError(int error) {
+        switch (error) {
+            case SatelliteError.ERROR_NONE:
+                return SatelliteManager.SATELLITE_ERROR_NONE;
+            case SatelliteError.SATELLITE_ERROR:
+                return SatelliteManager.SATELLITE_ERROR;
+            case SatelliteError.SERVER_ERROR:
+                return SatelliteManager.SATELLITE_SERVER_ERROR;
+            case SatelliteError.SERVICE_ERROR:
+                return SatelliteManager.SATELLITE_SERVICE_ERROR;
+            case SatelliteError.MODEM_ERROR:
+                return SatelliteManager.SATELLITE_MODEM_ERROR;
+            case SatelliteError.NETWORK_ERROR:
+                return SatelliteManager.SATELLITE_NETWORK_ERROR;
+            case SatelliteError.INVALID_TELEPHONY_STATE:
+                return SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE;
+            case SatelliteError.INVALID_MODEM_STATE:
+                return SatelliteManager.SATELLITE_INVALID_MODEM_STATE;
+            case SatelliteError.INVALID_ARGUMENTS:
+                return SatelliteManager.SATELLITE_INVALID_ARGUMENTS;
+            case SatelliteError.REQUEST_FAILED:
+                return SatelliteManager.SATELLITE_REQUEST_FAILED;
+            case SatelliteError.RADIO_NOT_AVAILABLE:
+                return SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE;
+            case SatelliteError.REQUEST_NOT_SUPPORTED:
+                return SatelliteManager.SATELLITE_REQUEST_NOT_SUPPORTED;
+            case SatelliteError.NO_RESOURCES:
+                return SatelliteManager.SATELLITE_NO_RESOURCES;
+            case SatelliteError.SERVICE_NOT_PROVISIONED:
+                return SatelliteManager.SATELLITE_SERVICE_NOT_PROVISIONED;
+            case SatelliteError.SERVICE_PROVISION_IN_PROGRESS:
+                return SatelliteManager.SATELLITE_SERVICE_PROVISION_IN_PROGRESS;
+            case SatelliteError.REQUEST_ABORTED:
+                return SatelliteManager.SATELLITE_REQUEST_ABORTED;
+            case SatelliteError.SATELLITE_ACCESS_BARRED:
+                return SatelliteManager.SATELLITE_ACCESS_BARRED;
+            case SatelliteError.NETWORK_TIMEOUT:
+                return SatelliteManager.SATELLITE_NETWORK_TIMEOUT;
+            case SatelliteError.SATELLITE_NOT_REACHABLE:
+                return SatelliteManager.SATELLITE_NOT_REACHABLE;
+            case SatelliteError.NOT_AUTHORIZED:
+                return SatelliteManager.SATELLITE_NOT_AUTHORIZED;
+        }
+        loge("Received invalid satellite service error: " + error);
+        return SatelliteManager.SATELLITE_SERVICE_ERROR;
+    }
+
+    /**
+     * Convert satellite modem state from service definition to framework definition.
+     * @param modemState The SatelliteModemState from the satellite service.
+     * @return The converted SatelliteModemState for the framework.
+     */
+    @SatelliteManager.SatelliteModemState
+    public static int fromSatelliteModemState(int modemState) {
+        switch (modemState) {
+            case SatelliteModemState.SATELLITE_MODEM_STATE_IDLE:
+                return SatelliteManager.SATELLITE_MODEM_STATE_IDLE;
+            case SatelliteModemState.SATELLITE_MODEM_STATE_LISTENING:
+                return SatelliteManager.SATELLITE_MODEM_STATE_LISTENING;
+            case SatelliteModemState.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING:
+                return SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING;
+            case SatelliteModemState.SATELLITE_MODEM_STATE_DATAGRAM_RETRYING:
+                return SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_RETRYING;
+            case SatelliteModemState.SATELLITE_MODEM_STATE_OFF:
+                return SatelliteManager.SATELLITE_MODEM_STATE_OFF;
+            case SatelliteModemState.SATELLITE_MODEM_STATE_UNAVAILABLE:
+                return SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE;
+            default:
+                loge("Received invalid modem state: " + modemState);
+                return SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN;
+        }
+    }
+
+    /**
+     * Convert SatelliteCapabilities from service definition to framework definition.
+     * @param capabilities The SatelliteCapabilities from the satellite service.
+     * @return The converted SatelliteCapabilities for the framework.
+     */
+    @Nullable public static SatelliteCapabilities fromSatelliteCapabilities(
+            @Nullable android.telephony.satellite.stub.SatelliteCapabilities capabilities) {
+        if (capabilities == null) return null;
+        int[] radioTechnologies = capabilities.supportedRadioTechnologies == null
+                ? new int[0] : capabilities.supportedRadioTechnologies;
+
+        Map<Integer, AntennaPosition> antennaPositionMap = new HashMap<>();
+        int[] antennaPositionKeys = capabilities.antennaPositionKeys;
+        AntennaPosition[] antennaPositionValues = capabilities.antennaPositionValues;
+        if (antennaPositionKeys != null && antennaPositionValues != null &&
+                antennaPositionKeys.length == antennaPositionValues.length) {
+            for(int i = 0; i < antennaPositionKeys.length; i++) {
+                antennaPositionMap.put(antennaPositionKeys[i], antennaPositionValues[i]);
+            }
+        }
+
+        return new SatelliteCapabilities(
+                Arrays.stream(radioTechnologies)
+                        .map(SatelliteServiceUtils::fromSatelliteRadioTechnology)
+                        .boxed().collect(Collectors.toSet()),
+                capabilities.isPointingRequired, capabilities.maxBytesPerOutgoingDatagram,
+                antennaPositionMap);
+    }
+
+    /**
+     * Convert PointingInfo from service definition to framework definition.
+     * @param pointingInfo The PointingInfo from the satellite service.
+     * @return The converted PointingInfo for the framework.
+     */
+    @Nullable public static PointingInfo fromPointingInfo(
+            android.telephony.satellite.stub.PointingInfo pointingInfo) {
+        if (pointingInfo == null) return null;
+        return new PointingInfo(pointingInfo.satelliteAzimuth, pointingInfo.satelliteElevation);
+    }
+
+    /**
+     * Convert SatelliteDatagram from service definition to framework definition.
+     * @param datagram The SatelliteDatagram from the satellite service.
+     * @return The converted SatelliteDatagram for the framework.
+     */
+    @Nullable public static SatelliteDatagram fromSatelliteDatagram(
+            android.telephony.satellite.stub.SatelliteDatagram datagram) {
+        if (datagram == null) return null;
+        byte[] data = datagram.data == null ? new byte[0] : datagram.data;
+        return new SatelliteDatagram(data);
+    }
+
+    /**
+     * Convert SatelliteDatagram from framework definition to service definition.
+     * @param datagram The SatelliteDatagram from the framework.
+     * @return The converted SatelliteDatagram for the satellite service.
+     */
+    @Nullable public static android.telephony.satellite.stub.SatelliteDatagram toSatelliteDatagram(
+            @Nullable SatelliteDatagram datagram) {
+        android.telephony.satellite.stub.SatelliteDatagram converted =
+                new android.telephony.satellite.stub.SatelliteDatagram();
+        converted.data = datagram.getSatelliteDatagram();
+        return converted;
+    }
+
+    /**
+     * Get the {@link SatelliteManager.SatelliteError} from the provided result.
+     *
+     * @param ar AsyncResult used to determine the error code.
+     * @param caller The satellite request.
+     *
+     * @return The {@link SatelliteManager.SatelliteError} error code from the request.
+     */
+    @SatelliteManager.SatelliteError public static int getSatelliteError(@NonNull AsyncResult ar,
+            @NonNull String caller) {
+        int errorCode;
+        if (ar.exception == null) {
+            errorCode = SatelliteManager.SATELLITE_ERROR_NONE;
+        } else {
+            errorCode = SatelliteManager.SATELLITE_ERROR;
+            if (ar.exception instanceof CommandException) {
+                CommandException.Error error = ((CommandException) ar.exception).getCommandError();
+                errorCode = RILUtils.convertToSatelliteError(error);
+                loge(caller + " CommandException: " + ar.exception);
+            } else if (ar.exception instanceof SatelliteManager.SatelliteException) {
+                errorCode = ((SatelliteManager.SatelliteException) ar.exception).getErrorCode();
+                loge(caller + " SatelliteException: " + ar.exception);
+            } else {
+                loge(caller + " unknown exception: " + ar.exception);
+            }
+        }
+        logd(caller + " error: " + errorCode);
+        return errorCode;
+    }
+
+    /**
+     * Get valid subscription id for satellite communication.
+     *
+     * @param subId The subscription id.
+     * @return input subId if the subscription is active else return default subscription id.
+     */
+    public static int getValidSatelliteSubId(int subId, @NonNull Context context) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            boolean isActive = SubscriptionManagerService.getInstance().isActiveSubId(subId,
+                    context.getOpPackageName(), context.getAttributionTag());
+
+            if (isActive) {
+                return subId;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+        logd("getValidSatelliteSubId: use DEFAULT_SUBSCRIPTION_ID for subId=" + subId);
+        return SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+    }
+
+    /**
+     * Return phone associated with phoneId 0.
+     *
+     * @return phone associated with phoneId 0 or {@code null} if it doesn't exist.
+     */
+    public static @Nullable Phone getPhone() {
+        return PhoneFactory.getPhone(0);
+    }
+
+    private static void logd(@NonNull String log) {
+        Rlog.d(TAG, log);
+    }
+
+    private static void loge(@NonNull String log) {
+        Rlog.e(TAG, log);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java b/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
new file mode 100644
index 0000000..36ad250
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
@@ -0,0 +1,742 @@
+/*
+ * 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.satellite;
+
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS;
+
+import 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.Build;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.provider.DeviceConfig;
+import android.telephony.Rlog;
+import android.telephony.satellite.ISatelliteStateCallback;
+import android.telephony.satellite.SatelliteManager;
+import android.telephony.satellite.stub.ISatelliteGateway;
+import android.telephony.satellite.stub.SatelliteGatewayService;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.ExponentialBackoff;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This module is responsible for managing session state transition and inform listeners of modem
+ * state changed events accordingly.
+ */
+public class SatelliteSessionController extends StateMachine {
+    private static final String TAG = "SatelliteSessionController";
+    private static final boolean DBG = true;
+    private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem";
+    private static final boolean DEBUG = !"user".equals(Build.TYPE);
+
+    /**
+     * The time duration in millis that the satellite will stay at listening mode to wait for the
+     * next incoming page before disabling listening mode when transitioning from sending mode.
+     */
+    public static final String SATELLITE_STAY_AT_LISTENING_FROM_SENDING_MILLIS =
+            "satellite_stay_at_listening_from_sending_millis";
+    /**
+     * The default value of {@link #SATELLITE_STAY_AT_LISTENING_FROM_SENDING_MILLIS}.
+     */
+    public static final long DEFAULT_SATELLITE_STAY_AT_LISTENING_FROM_SENDING_MILLIS = 180000;
+    /**
+     * The time duration in millis that the satellite will stay at listening mode to wait for the
+     * next incoming page before disabling listening mode when transitioning from receiving mode.
+     */
+    public static final String SATELLITE_STAY_AT_LISTENING_FROM_RECEIVING_MILLIS =
+            "satellite_stay_at_listening_from_receiving_millis";
+    /**
+     * The default value of {@link #SATELLITE_STAY_AT_LISTENING_FROM_RECEIVING_MILLIS}
+     */
+    public static final long DEFAULT_SATELLITE_STAY_AT_LISTENING_FROM_RECEIVING_MILLIS = 30000;
+    /**
+     * The default value of {@link #SATELLITE_STAY_AT_LISTENING_FROM_SENDING_MILLIS},
+     * and {@link #SATELLITE_STAY_AT_LISTENING_FROM_RECEIVING_MILLIS} for demo mode
+     */
+    public static final long DEMO_MODE_SATELLITE_STAY_AT_LISTENING_MILLIS = 3000;
+
+    private static final int EVENT_DATAGRAM_TRANSFER_STATE_CHANGED = 1;
+    private static final int EVENT_LISTENING_TIMER_TIMEOUT = 2;
+    private static final int EVENT_SATELLITE_ENABLED_STATE_CHANGED = 3;
+
+    private static final long REBIND_INITIAL_DELAY = 2 * 1000; // 2 seconds
+    private static final long REBIND_MAXIMUM_DELAY = 64 * 1000; // 1 minute
+    private static final int REBIND_MULTIPLIER = 2;
+    @NonNull private final ExponentialBackoff mExponentialBackoff;
+    @NonNull private final Object mLock = new Object();
+    @Nullable
+    private ISatelliteGateway mSatelliteGatewayService;
+    private String mSatelliteGatewayServicePackageName = "";
+    @Nullable private SatelliteGatewayServiceConnection mSatelliteGatewayServiceConnection;
+    private boolean mIsBound;
+    private boolean mIsBinding;
+
+    @NonNull private static SatelliteSessionController sInstance;
+
+    @NonNull private final Context mContext;
+    @NonNull private final SatelliteModemInterface mSatelliteModemInterface;
+    @NonNull private final UnavailableState mUnavailableState = new UnavailableState();
+    @NonNull private final PowerOffState mPowerOffState = new PowerOffState();
+    @NonNull private final IdleState mIdleState = new IdleState();
+    @NonNull private final TransferringState mTransferringState = new TransferringState();
+    @NonNull private final ListeningState mListeningState = new ListeningState();
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected AtomicBoolean mIsSendingTriggeredDuringTransferringState;
+    private long mSatelliteStayAtListeningFromSendingMillis;
+    private long mSatelliteStayAtListeningFromReceivingMillis;
+    private final ConcurrentHashMap<IBinder, ISatelliteStateCallback> mListeners;
+    @SatelliteManager.SatelliteModemState private int mCurrentState;
+    final boolean mIsSatelliteSupported;
+    private boolean mIsDemoMode = false;
+
+    /**
+     * @return The singleton instance of SatelliteSessionController.
+     */
+    public static SatelliteSessionController getInstance() {
+        if (sInstance == null) {
+            Log.e(TAG, "SatelliteSessionController was not yet initialized.");
+        }
+        return sInstance;
+    }
+
+    /**
+     * Create the SatelliteSessionController singleton instance.
+     *
+     * @param context The Context for the SatelliteSessionController.
+     * @param looper The looper associated with the handler of this class.
+     * @param isSatelliteSupported Whether satellite is supported on the device.
+     * @return The singleton instance of SatelliteSessionController.
+     */
+    public static SatelliteSessionController make(
+            @NonNull Context context, @NonNull Looper looper, boolean isSatelliteSupported) {
+        if (sInstance == null) {
+            sInstance = new SatelliteSessionController(context, looper, isSatelliteSupported,
+                    SatelliteModemInterface.getInstance(),
+                    getSatelliteStayAtListeningFromSendingMillis(),
+                    getSatelliteStayAtListeningFromReceivingMillis());
+        } else {
+            if (isSatelliteSupported != sInstance.mIsSatelliteSupported) {
+                Rlog.e(TAG, "New satellite support state " + isSatelliteSupported
+                        + " is different from existing state " + sInstance.mIsSatelliteSupported
+                        + ". Ignore the new state.");
+            }
+        }
+        return sInstance;
+    }
+
+    /**
+     * Create a SatelliteSessionController to manage satellite session.
+     *
+     * @param context The Context for the SatelliteSessionController.
+     * @param looper The looper associated with the handler of this class.
+     * @param isSatelliteSupported Whether satellite is supported on the device.
+     * @param satelliteModemInterface The singleton of SatelliteModemInterface.
+     * @param satelliteStayAtListeningFromSendingMillis The duration to stay at listening mode when
+     *                                                    transitioning from sending mode.
+     * @param satelliteStayAtListeningFromReceivingMillis The duration to stay at listening mode
+     *                                                    when transitioning from receiving mode.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected SatelliteSessionController(@NonNull Context context, @NonNull Looper looper,
+            boolean isSatelliteSupported,
+            @NonNull SatelliteModemInterface satelliteModemInterface,
+            long satelliteStayAtListeningFromSendingMillis,
+            long satelliteStayAtListeningFromReceivingMillis) {
+        super(TAG, looper);
+
+        mContext = context;
+        mSatelliteModemInterface = satelliteModemInterface;
+        mSatelliteStayAtListeningFromSendingMillis = satelliteStayAtListeningFromSendingMillis;
+        mSatelliteStayAtListeningFromReceivingMillis = satelliteStayAtListeningFromReceivingMillis;
+        mListeners = new ConcurrentHashMap<>();
+        mIsSendingTriggeredDuringTransferringState = new AtomicBoolean(false);
+        mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN;
+        mIsSatelliteSupported = isSatelliteSupported;
+        mExponentialBackoff = new ExponentialBackoff(REBIND_INITIAL_DELAY, REBIND_MAXIMUM_DELAY,
+                REBIND_MULTIPLIER, looper, () -> {
+            synchronized (mLock) {
+                if ((mIsBound && mSatelliteGatewayService != null) || mIsBinding) {
+                    return;
+                }
+            }
+            if (mSatelliteGatewayServiceConnection != null) {
+                synchronized (mLock) {
+                    mIsBound = false;
+                    mIsBinding = false;
+                }
+                unbindService();
+            }
+            bindService();
+        });
+
+        addState(mUnavailableState);
+        addState(mPowerOffState);
+        addState(mIdleState);
+        addState(mTransferringState);
+        addState(mListeningState, mTransferringState);
+        setInitialState(isSatelliteSupported);
+        start();
+    }
+
+    /**
+     * {@link DatagramController} uses this function to notify {@link SatelliteSessionController}
+     * that its datagram transfer state has changed.
+     *
+     * @param sendState The current datagram send state of {@link DatagramController}.
+     * @param receiveState The current datagram receive state of {@link DatagramController}.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void onDatagramTransferStateChanged(
+            @SatelliteManager.SatelliteDatagramTransferState int sendState,
+            @SatelliteManager.SatelliteDatagramTransferState int receiveState) {
+        sendMessage(EVENT_DATAGRAM_TRANSFER_STATE_CHANGED,
+                new DatagramTransferState(sendState, receiveState));
+        if (sendState == SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING) {
+            mIsSendingTriggeredDuringTransferringState.set(true);
+        }
+    }
+
+    /**
+     * {@link SatelliteController} uses this function to notify {@link SatelliteSessionController}
+     * that the satellite enabled state has changed.
+     *
+     * @param enabled {@code true} means enabled and {@code false} means disabled.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void onSatelliteEnabledStateChanged(boolean enabled) {
+        sendMessage(EVENT_SATELLITE_ENABLED_STATE_CHANGED, enabled);
+    }
+
+    /**
+     * Registers for modem state changed from satellite modem.
+     *
+     * @param callback The callback to handle the satellite modem state changed event.
+     */
+    public void registerForSatelliteModemStateChanged(@NonNull ISatelliteStateCallback callback) {
+        try {
+            callback.onSatelliteModemStateChanged(mCurrentState);
+            mListeners.put(callback.asBinder(), callback);
+        } catch (RemoteException ex) {
+            loge("registerForSatelliteModemStateChanged: Got RemoteException ex=" + ex);
+        }
+    }
+
+    /**
+     * Unregisters for modem state changed from satellite modem.
+     * If callback was not registered before, the request will be ignored.
+     *
+     * @param callback The callback that was passed to
+     *                 {@link #registerForSatelliteModemStateChanged(ISatelliteStateCallback)}.
+     */
+    public void unregisterForSatelliteModemStateChanged(@NonNull ISatelliteStateCallback callback) {
+        mListeners.remove(callback.asBinder());
+    }
+
+    /**
+     * This API can be used by only CTS to update the timeout duration in milliseconds that
+     * satellite should stay at listening mode to wait for the next incoming page before disabling
+     * listening mode.
+     *
+     * @param timeoutMillis The timeout duration in millisecond.
+     * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise.
+     */
+    boolean setSatelliteListeningTimeoutDuration(long timeoutMillis) {
+        if (!isMockModemAllowed()) {
+            loge("Updating listening timeout duration is not allowed");
+            return false;
+        }
+
+        logd("setSatelliteListeningTimeoutDuration: timeoutMillis=" + timeoutMillis);
+        if (timeoutMillis == 0) {
+            mSatelliteStayAtListeningFromSendingMillis =
+                    getSatelliteStayAtListeningFromSendingMillis();
+            mSatelliteStayAtListeningFromReceivingMillis =
+                    getSatelliteStayAtListeningFromReceivingMillis();
+        } else {
+            mSatelliteStayAtListeningFromSendingMillis = timeoutMillis;
+            mSatelliteStayAtListeningFromReceivingMillis = timeoutMillis;
+        }
+
+        return true;
+    }
+
+    /**
+     * This API can be used by only CTS to update satellite gateway service package name.
+     *
+     * @param servicePackageName The package name of the satellite gateway service.
+     * @return {@code true} if the satellite gateway service is set successfully,
+     * {@code false} otherwise.
+     */
+    boolean setSatelliteGatewayServicePackageName(@Nullable String servicePackageName) {
+        if (!isMockModemAllowed()) {
+            loge("setSatelliteGatewayServicePackageName: modifying satellite gateway service "
+                    + "package name is not allowed");
+            return false;
+        }
+
+        logd("setSatelliteGatewayServicePackageName: config_satellite_gateway_service_package is "
+                + "updated, new packageName=" + servicePackageName);
+
+        if (servicePackageName == null || servicePackageName.equals("null")) {
+            mSatelliteGatewayServicePackageName = "";
+        } else {
+            mSatelliteGatewayServicePackageName = servicePackageName;
+        }
+
+        if (mSatelliteGatewayServiceConnection != null) {
+            synchronized (mLock) {
+                mIsBound = false;
+                mIsBinding = false;
+            }
+            unbindService();
+            bindService();
+        }
+        return true;
+    }
+    /**
+     * Adjusts listening timeout duration when demo mode is on
+     *
+     * @param isDemoMode {@code true} : The listening timeout durations will be set to
+     *                   {@link #DEMO_MODE_SATELLITE_STAY_AT_LISTENING_MILLIS}
+     *                   {@code false} : The listening timeout durations will be restored to
+     *                   production mode
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void setDemoMode(boolean isDemoMode) {
+        mIsDemoMode = isDemoMode;
+    }
+
+    private boolean isDemoMode() {
+        return mIsDemoMode;
+    }
+
+    private static class DatagramTransferState {
+        @SatelliteManager.SatelliteDatagramTransferState public int sendState;
+        @SatelliteManager.SatelliteDatagramTransferState public int receiveState;
+
+        DatagramTransferState(@SatelliteManager.SatelliteDatagramTransferState int sendState,
+                @SatelliteManager.SatelliteDatagramTransferState int receiveState) {
+            this.sendState = sendState;
+            this.receiveState = receiveState;
+        }
+    }
+
+    private class UnavailableState extends State {
+        @Override
+        public void enter() {
+            if (DBG) logd("Entering UnavailableState");
+            mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE;
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            loge("UnavailableState: receive msg " + getWhatToString(msg.what) + " unexpectedly");
+            return HANDLED;
+        }
+    }
+
+    private class PowerOffState extends State {
+        @Override
+        public void enter() {
+            if (DBG) logd("Entering PowerOffState");
+
+            mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_OFF;
+            mIsSendingTriggeredDuringTransferringState.set(false);
+            unbindService();
+            notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+        }
+
+        @Override
+        public void exit() {
+            if (DBG) logd("Exiting PowerOffState");
+            logd("Attempting to bind to SatelliteGatewayService.");
+            bindService();
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (DBG) log("PowerOffState: processing " + getWhatToString(msg.what));
+            switch (msg.what) {
+                case EVENT_SATELLITE_ENABLED_STATE_CHANGED:
+                    handleSatelliteEnabledStateChanged((boolean) msg.obj);
+                    break;
+            }
+            // Ignore all unexpected events.
+            return HANDLED;
+        }
+
+        private void handleSatelliteEnabledStateChanged(boolean on) {
+            if (on) {
+                transitionTo(mIdleState);
+            }
+        }
+    }
+
+    private class IdleState extends State {
+        @Override
+        public void enter() {
+            if (DBG) logd("Entering IdleState");
+            mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_IDLE;
+            mIsSendingTriggeredDuringTransferringState.set(false);
+            //Enable Cellular Modem scanning
+            mSatelliteModemInterface.enableCellularModemWhileSatelliteModeIsOn(true, null);
+            notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (DBG) log("IdleState: processing " + getWhatToString(msg.what));
+            switch (msg.what) {
+                case EVENT_DATAGRAM_TRANSFER_STATE_CHANGED:
+                    handleEventDatagramTransferStateChanged((DatagramTransferState) msg.obj);
+                    break;
+                case EVENT_SATELLITE_ENABLED_STATE_CHANGED:
+                    handleSatelliteEnabledStateChanged(!(boolean) msg.obj, "IdleState");
+                    break;
+            }
+            // Ignore all unexpected events.
+            return HANDLED;
+        }
+
+        private void handleEventDatagramTransferStateChanged(
+                @NonNull DatagramTransferState datagramTransferState) {
+            if ((datagramTransferState.sendState == SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING)
+                    || (datagramTransferState.receiveState
+                    == SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING)) {
+                transitionTo(mTransferringState);
+            }
+        }
+
+        @Override
+        public void exit() {
+            if (DBG) logd("Exiting IdleState");
+            //Disable Cellular Modem Scanning
+            mSatelliteModemInterface.enableCellularModemWhileSatelliteModeIsOn(false, null);
+        }
+    }
+
+    private class TransferringState extends State {
+        @Override
+        public void enter() {
+            if (DBG) logd("Entering TransferringState");
+            mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING;
+            notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (DBG) log("TransferringState: processing " + getWhatToString(msg.what));
+            switch (msg.what) {
+                case EVENT_DATAGRAM_TRANSFER_STATE_CHANGED:
+                    handleEventDatagramTransferStateChanged((DatagramTransferState) msg.obj);
+                    return HANDLED;
+                case EVENT_SATELLITE_ENABLED_STATE_CHANGED:
+                    handleSatelliteEnabledStateChanged(!(boolean) msg.obj, "TransferringState");
+                    break;
+            }
+            // Ignore all unexpected events.
+            return HANDLED;
+        }
+
+        private void handleEventDatagramTransferStateChanged(
+                @NonNull DatagramTransferState datagramTransferState) {
+            if (isSending(datagramTransferState.sendState) || isReceiving(
+                    datagramTransferState.receiveState)) {
+                // Stay at transferring state.
+            } else if ((datagramTransferState.sendState
+                    == SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED)
+                    || (datagramTransferState.receiveState
+                    == SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED)) {
+                transitionTo(mIdleState);
+            } else {
+                transitionTo(mListeningState);
+            }
+        }
+    }
+
+    private class ListeningState extends State {
+        @Override
+        public void enter() {
+            if (DBG) logd("Entering ListeningState");
+
+            mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_LISTENING;
+            long timeoutMillis = updateListeningMode(true);
+            sendMessageDelayed(EVENT_LISTENING_TIMER_TIMEOUT, timeoutMillis);
+            mIsSendingTriggeredDuringTransferringState.set(false);
+            notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_LISTENING);
+        }
+
+        @Override
+        public void exit() {
+            removeMessages(EVENT_LISTENING_TIMER_TIMEOUT);
+            updateListeningMode(false);
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (DBG) log("ListeningState: processing " + getWhatToString(msg.what));
+            switch (msg.what) {
+                case EVENT_LISTENING_TIMER_TIMEOUT:
+                    transitionTo(mIdleState);
+                    break;
+                case EVENT_DATAGRAM_TRANSFER_STATE_CHANGED:
+                    handleEventDatagramTransferStateChanged((DatagramTransferState) msg.obj);
+                    break;
+                case EVENT_SATELLITE_ENABLED_STATE_CHANGED:
+                    handleSatelliteEnabledStateChanged(!(boolean) msg.obj, "ListeningState");
+                    break;
+            }
+            // Ignore all unexpected events.
+            return HANDLED;
+        }
+
+        private long updateListeningMode(boolean enabled) {
+            long timeoutMillis;
+            if (mIsSendingTriggeredDuringTransferringState.get()) {
+                timeoutMillis = mSatelliteStayAtListeningFromSendingMillis;
+            } else {
+                timeoutMillis = mSatelliteStayAtListeningFromReceivingMillis;
+            }
+            mSatelliteModemInterface.requestSatelliteListeningEnabled(
+                    enabled, (int) timeoutMillis, null);
+            return timeoutMillis;
+        }
+
+        private void handleEventDatagramTransferStateChanged(
+                @NonNull DatagramTransferState datagramTransferState) {
+            if (datagramTransferState.sendState == SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING
+                    || datagramTransferState.receiveState
+                    == SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING) {
+                transitionTo(mTransferringState);
+            }
+        }
+    }
+
+    /**
+     * @return the string for msg.what
+     */
+    @Override
+    protected String getWhatToString(int what) {
+        String whatString;
+        switch (what) {
+            case EVENT_DATAGRAM_TRANSFER_STATE_CHANGED:
+                whatString = "EVENT_DATAGRAM_TRANSFER_STATE_CHANGED";
+                break;
+            case EVENT_LISTENING_TIMER_TIMEOUT:
+                whatString = "EVENT_LISTENING_TIMER_TIMEOUT";
+                break;
+            case EVENT_SATELLITE_ENABLED_STATE_CHANGED:
+                whatString = "EVENT_SATELLITE_ENABLED_STATE_CHANGED";
+                break;
+            default:
+                whatString = "UNKNOWN EVENT " + what;
+        }
+        return whatString;
+    }
+
+    private void setInitialState(boolean isSatelliteSupported) {
+        if (isSatelliteSupported) {
+            setInitialState(mPowerOffState);
+        } else {
+            setInitialState(mUnavailableState);
+        }
+    }
+
+    private void notifyStateChangedEvent(@SatelliteManager.SatelliteModemState int state) {
+        List<ISatelliteStateCallback> toBeRemoved = new ArrayList<>();
+        mListeners.values().forEach(listener -> {
+            try {
+                listener.onSatelliteModemStateChanged(state);
+            } catch (RemoteException e) {
+                logd("notifyStateChangedEvent RemoteException: " + e);
+                toBeRemoved.add(listener);
+            }
+        });
+
+        toBeRemoved.forEach(listener -> {
+            mListeners.remove(listener.asBinder());
+        });
+    }
+
+    private void handleSatelliteEnabledStateChanged(boolean off, String caller) {
+        if (off) {
+            transitionTo(mPowerOffState);
+        } else {
+            loge(caller + ": Unexpected satellite radio powered-on state changed event");
+        }
+    }
+
+    private boolean isSending(@SatelliteManager.SatelliteDatagramTransferState int sendState) {
+        return (sendState == SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING
+                || sendState == SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS);
+    }
+
+    private boolean isReceiving(@SatelliteManager.SatelliteDatagramTransferState int receiveState) {
+        return (receiveState == SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING
+                || receiveState == SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS
+                || receiveState == SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE);
+    }
+
+    @NonNull
+    private String getSatelliteGatewayPackageName() {
+        if (!TextUtils.isEmpty(mSatelliteGatewayServicePackageName)) {
+            return mSatelliteGatewayServicePackageName;
+        }
+        return TextUtils.emptyIfNull(mContext.getResources().getString(
+                R.string.config_satellite_gateway_service_package));
+    }
+
+    private void bindService() {
+        synchronized (mLock) {
+            if (mIsBinding || mIsBound) return;
+            mIsBinding = true;
+        }
+        mExponentialBackoff.start();
+
+        String packageName = getSatelliteGatewayPackageName();
+        if (TextUtils.isEmpty(packageName)) {
+            loge("Unable to bind to the satellite gateway service because the package is"
+                    + " undefined.");
+            // Since the package name comes from static device configs, stop retry because
+            // rebind will continue to fail without a valid package name.
+            synchronized (mLock) {
+                mIsBinding = false;
+            }
+            mExponentialBackoff.stop();
+            return;
+        }
+        Intent intent = new Intent(SatelliteGatewayService.SERVICE_INTERFACE);
+        intent.setPackage(packageName);
+
+        mSatelliteGatewayServiceConnection = new SatelliteGatewayServiceConnection();
+        try {
+            boolean success = mContext.bindService(
+                    intent, mSatelliteGatewayServiceConnection, Context.BIND_AUTO_CREATE);
+            if (success) {
+                logd("Successfully bound to the satellite gateway service.");
+            } else {
+                synchronized (mLock) {
+                    mIsBinding = false;
+                }
+                mExponentialBackoff.notifyFailed();
+                loge("Error binding to the satellite gateway service. Retrying in "
+                        + mExponentialBackoff.getCurrentDelay() + " ms.");
+            }
+        } catch (Exception e) {
+            synchronized (mLock) {
+                mIsBinding = false;
+            }
+            mExponentialBackoff.notifyFailed();
+            loge("Exception binding to the satellite gateway service. Retrying in "
+                    + mExponentialBackoff.getCurrentDelay() + " ms. Exception: " + e);
+        }
+    }
+
+    private void unbindService() {
+        logd("unbindService");
+        mExponentialBackoff.stop();
+        mSatelliteGatewayService = null;
+        synchronized (mLock) {
+            mIsBinding = false;
+            mIsBound = false;
+        }
+        if (mSatelliteGatewayServiceConnection != null) {
+            mContext.unbindService(mSatelliteGatewayServiceConnection);
+            mSatelliteGatewayServiceConnection = null;
+        }
+    }
+    private class SatelliteGatewayServiceConnection implements ServiceConnection {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            logd("onServiceConnected: ComponentName=" + name);
+            synchronized (mLock) {
+                mIsBound = true;
+                mIsBinding = false;
+            }
+            mSatelliteGatewayService = ISatelliteGateway.Stub.asInterface(service);
+            mExponentialBackoff.stop();
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            loge("onServiceDisconnected: Waiting for reconnect.");
+            synchronized (mLock) {
+                mIsBinding = false;
+                mIsBound = false;
+            }
+            mSatelliteGatewayService = null;
+        }
+
+        @Override
+        public void onBindingDied(ComponentName name) {
+            loge("onBindingDied: Unbinding and rebinding service.");
+            synchronized (mLock) {
+                mIsBound = false;
+                mIsBinding = false;
+            }
+            unbindService();
+            mExponentialBackoff.start();
+        }
+    }
+
+    private boolean isMockModemAllowed() {
+        return (DEBUG || SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false));
+    }
+
+    private static long getSatelliteStayAtListeningFromSendingMillis() {
+        if (sInstance != null && sInstance.isDemoMode()) {
+            return DEMO_MODE_SATELLITE_STAY_AT_LISTENING_MILLIS;
+        } else {
+            return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
+                    SATELLITE_STAY_AT_LISTENING_FROM_SENDING_MILLIS,
+                    DEFAULT_SATELLITE_STAY_AT_LISTENING_FROM_SENDING_MILLIS);
+        }
+    }
+
+    private static long getSatelliteStayAtListeningFromReceivingMillis() {
+        if (sInstance != null && sInstance.isDemoMode()) {
+            return DEMO_MODE_SATELLITE_STAY_AT_LISTENING_MILLIS;
+        } else {
+            return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
+                    SATELLITE_STAY_AT_LISTENING_FROM_RECEIVING_MILLIS,
+                    DEFAULT_SATELLITE_STAY_AT_LISTENING_FROM_RECEIVING_MILLIS);
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/satellite/metrics/ControllerMetricsStats.java b/src/java/com/android/internal/telephony/satellite/metrics/ControllerMetricsStats.java
new file mode 100644
index 0000000..7a1de7c
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/metrics/ControllerMetricsStats.java
@@ -0,0 +1,370 @@
+/*
+ * 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.satellite.metrics;
+
+import android.annotation.NonNull;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+import android.telephony.satellite.SatelliteManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.metrics.SatelliteStats;
+
+/**
+ * Stats to log to satellite metrics
+ */
+public class ControllerMetricsStats {
+    private static final int ADD_COUNT = 1;
+    private static final String TAG = ControllerMetricsStats.class.getSimpleName();
+    private static final boolean DBG = false;
+
+    private static ControllerMetricsStats sInstance;
+
+    private final Context mContext;
+    private SatelliteStats mSatelliteStats;
+
+    private long mSatelliteOnTimeMillis;
+    private int mBatteryLevelWhenServiceOn;
+    private boolean mIsSatelliteModemOn;
+    private Boolean mIsBatteryCharged = null;
+    private int mBatteryChargedStartTimeSec;
+    private int mTotalBatteryChargeTimeSec;
+
+    /**
+     * @return The singleton instance of ControllerMetricsStats.
+     */
+    public static ControllerMetricsStats getInstance() {
+        if (sInstance == null) {
+            loge("ControllerMetricsStats was not yet initialized.");
+        }
+        return sInstance;
+    }
+
+    /**
+     * Create the ControllerMetricsStats singleton instance.
+     *
+     * @param context   The Context for the ControllerMetricsStats.
+     * @return          The singleton instance of ControllerMetricsStats.
+     */
+    public static ControllerMetricsStats make(@NonNull Context context) {
+        if (sInstance == null) {
+            sInstance = new ControllerMetricsStats(context);
+        }
+        return sInstance;
+    }
+
+    /**
+     * Create the ControllerMetricsStats singleton instance, testing purpose only.
+     *
+     * @param context   The Context for the ControllerMetricsStats.
+     * @param satelliteStats SatelliteStats instance to test
+     * @return          The singleton instance of ControllerMetricsStats.
+     */
+    @VisibleForTesting
+    public static ControllerMetricsStats make(@NonNull Context context,
+            @NonNull SatelliteStats satelliteStats) {
+        if (sInstance == null) {
+            sInstance = new ControllerMetricsStats(context, satelliteStats);
+        }
+        return sInstance;
+    }
+
+    /**
+     * Create the ControllerMetricsStats to manage metrics report for
+     * {@link SatelliteStats.SatelliteControllerParams}
+     * @param context The Context for the ControllerMetricsStats.
+     */
+    ControllerMetricsStats(@NonNull Context context) {
+        mContext = context;
+        mSatelliteStats = SatelliteStats.getInstance();
+    }
+
+    /**
+     * Create the ControllerMetricsStats to manage metrics report for
+     * {@link SatelliteStats.SatelliteControllerParams}
+     *
+     * @param context           The Context for the ControllerMetricsStats.
+     * @param satelliteStats    SatelliteStats object used for testing purpose
+     */
+    @VisibleForTesting
+    protected ControllerMetricsStats(@NonNull Context context,
+            @NonNull SatelliteStats satelliteStats) {
+        mContext = context;
+        mSatelliteStats = satelliteStats;
+    }
+
+
+    /** Report a counter when an attempt for satellite service on is successfully done */
+    public void reportServiceEnablementSuccessCount() {
+        logd("reportServiceEnablementSuccessCount()");
+        mSatelliteStats.onSatelliteControllerMetrics(
+                new SatelliteStats.SatelliteControllerParams.Builder()
+                        .setCountOfSatelliteServiceEnablementsSuccess(ADD_COUNT)
+                        .build());
+    }
+
+    /** Report a counter when an attempt for satellite service on is failed */
+    public void reportServiceEnablementFailCount() {
+        logd("reportServiceEnablementSuccessCount()");
+        mSatelliteStats.onSatelliteControllerMetrics(
+                new SatelliteStats.SatelliteControllerParams.Builder()
+                        .setCountOfSatelliteServiceEnablementsFail(ADD_COUNT)
+                        .build());
+    }
+
+    /** Report a counter when an attempt for outgoing datagram is successfully done */
+    public void reportOutgoingDatagramSuccessCount(
+            @NonNull @SatelliteManager.DatagramType int datagramType) {
+        SatelliteStats.SatelliteControllerParams controllerParam;
+        if (datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) {
+            controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
+                    .setCountOfOutgoingDatagramSuccess(ADD_COUNT)
+                    .setCountOfDatagramTypeSosSmsSuccess(ADD_COUNT)
+                    .build();
+        } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING) {
+            controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
+                    .setCountOfOutgoingDatagramSuccess(ADD_COUNT)
+                    .setCountOfDatagramTypeLocationSharingSuccess(ADD_COUNT)
+                    .build();
+        } else { // datagramType == SatelliteManager.DATAGRAM_TYPE_UNKNOWN
+            controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
+                    .setCountOfOutgoingDatagramSuccess(ADD_COUNT)
+                    .build();
+        }
+        logd("reportServiceEnablementSuccessCount(): " + controllerParam);
+        mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
+    }
+
+    /** Report a counter when an attempt for outgoing datagram is failed */
+    public void reportOutgoingDatagramFailCount(
+            @NonNull @SatelliteManager.DatagramType int datagramType) {
+        SatelliteStats.SatelliteControllerParams controllerParam;
+        if (datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) {
+            controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
+                    .setCountOfOutgoingDatagramFail(ADD_COUNT)
+                    .setCountOfDatagramTypeSosSmsFail(ADD_COUNT)
+                    .build();
+        } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING) {
+            controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
+                    .setCountOfOutgoingDatagramFail(ADD_COUNT)
+                    .setCountOfDatagramTypeLocationSharingFail(ADD_COUNT)
+                    .build();
+        } else { // datagramType == SatelliteManager.DATAGRAM_TYPE_UNKNOWN
+            controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
+                    .setCountOfOutgoingDatagramFail(ADD_COUNT)
+                    .build();
+        }
+        logd("reportOutgoingDatagramFailCount(): " + controllerParam);
+        mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
+    }
+
+    /** Report a counter when an attempt for incoming datagram is failed */
+    public void reportIncomingDatagramCount(
+            @NonNull @SatelliteManager.SatelliteError int result) {
+        SatelliteStats.SatelliteControllerParams controllerParam;
+        if (result == SatelliteManager.SATELLITE_ERROR_NONE) {
+            controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
+                    .setCountOfIncomingDatagramSuccess(ADD_COUNT)
+                    .build();
+        } else {
+            controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
+                    .setCountOfIncomingDatagramFail(ADD_COUNT)
+                    .build();
+        }
+        logd("reportIncomingDatagramCount(): " + controllerParam);
+        mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
+    }
+
+    /** Report a counter when an attempt for de-provision is success or not */
+    public void reportProvisionCount(@NonNull @SatelliteManager.SatelliteError int result) {
+        SatelliteStats.SatelliteControllerParams controllerParam;
+        if (result == SatelliteManager.SATELLITE_ERROR_NONE) {
+            controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
+                    .setCountOfProvisionSuccess(ADD_COUNT)
+                    .build();
+        } else {
+            controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
+                    .setCountOfProvisionFail(ADD_COUNT)
+                    .build();
+        }
+        logd("reportProvisionCount(): " + controllerParam);
+        mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
+    }
+
+    /** Report a counter when an attempt for de-provision is success or not */
+    public void reportDeprovisionCount(@NonNull @SatelliteManager.SatelliteError int result) {
+        SatelliteStats.SatelliteControllerParams controllerParam;
+        if (result == SatelliteManager.SATELLITE_ERROR_NONE) {
+            controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
+                    .setCountOfDeprovisionSuccess(ADD_COUNT)
+                    .build();
+        } else {
+            controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
+                    .setCountOfDeprovisionFail(ADD_COUNT)
+                    .build();
+        }
+        logd("reportDeprovisionCount(): " + controllerParam);
+        mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
+    }
+
+    /** Return the total service up time for satellite service */
+    @VisibleForTesting
+    public int captureTotalServiceUpTimeSec() {
+        long totalTimeMillis = getCurrentTime() - mSatelliteOnTimeMillis;
+        mSatelliteOnTimeMillis = 0;
+        return (int) (totalTimeMillis / 1000);
+    }
+
+    /** Return the total battery charge time while satellite service is on */
+    @VisibleForTesting
+    public int captureTotalBatteryChargeTimeSec() {
+        int totalTime = mTotalBatteryChargeTimeSec;
+        mTotalBatteryChargeTimeSec = 0;
+        return totalTime;
+    }
+
+    /** Capture the satellite service on time and register battery monitor */
+    public void onSatelliteEnabled() {
+        if (!isSatelliteModemOn()) {
+            mIsSatelliteModemOn = true;
+
+            startCaptureBatteryLevel();
+
+            // log the timestamp of the satellite modem power on
+            mSatelliteOnTimeMillis = getCurrentTime();
+
+            // register broadcast receiver for monitoring battery status change
+            IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
+
+            logd("register BatteryStatusReceiver");
+            mContext.registerReceiver(mBatteryStatusReceiver, filter);
+        }
+    }
+
+    /** Capture the satellite service off time and de-register battery monitor */
+    public void onSatelliteDisabled() {
+        if (isSatelliteModemOn()) {
+            mIsSatelliteModemOn = false;
+
+            logd("unregister BatteryStatusReceiver");
+            mContext.unregisterReceiver(mBatteryStatusReceiver);
+
+            int totalServiceUpTime = captureTotalServiceUpTimeSec();
+            int batteryConsumptionPercent = captureTotalBatteryConsumptionPercent(mContext);
+            int totalBatteryChargeTime = captureTotalBatteryChargeTimeSec();
+
+            // report metrics about service up time and battery
+            SatelliteStats.SatelliteControllerParams controllerParam =
+                    new SatelliteStats.SatelliteControllerParams.Builder()
+                            .setTotalServiceUptimeSec(totalServiceUpTime)
+                            .setTotalBatteryConsumptionPercent(batteryConsumptionPercent)
+                            .setTotalBatteryChargedTimeSec(totalBatteryChargeTime)
+                            .build();
+            logd("onSatelliteDisabled(): " + controllerParam);
+            mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
+        }
+    }
+
+    /** Log the total battery charging time when satellite service is on */
+    private void updateSatelliteBatteryChargeTime(boolean isCharged) {
+        logd("updateSatelliteBatteryChargeTime(" + isCharged + ")");
+        // update only when the charge state has changed
+        if (mIsBatteryCharged == null || isCharged != mIsBatteryCharged) {
+            mIsBatteryCharged = isCharged;
+
+            // When charged, log the start time of battery charging
+            if (isCharged) {
+                mBatteryChargedStartTimeSec = (int) (getCurrentTime() / 1000);
+                // When discharged, log the accumulated total battery charging time.
+            } else {
+                mTotalBatteryChargeTimeSec +=
+                        (int) (getCurrentTime() / 1000)
+                                - mBatteryChargedStartTimeSec;
+                mBatteryChargedStartTimeSec = 0;
+            }
+        }
+    }
+
+    /** Capture the battery level when satellite service is on */
+    @VisibleForTesting
+    public void startCaptureBatteryLevel() {
+        try {
+            BatteryManager batteryManager = mContext.getSystemService(BatteryManager.class);
+            mBatteryLevelWhenServiceOn =
+                    batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
+            logd("sBatteryLevelWhenServiceOn = " + mBatteryLevelWhenServiceOn);
+        } catch (NullPointerException e) {
+            loge("BatteryManager is null");
+        }
+    }
+
+    /** Capture the total consumption level when service is off */
+    @VisibleForTesting
+    public int captureTotalBatteryConsumptionPercent(Context context) {
+        try {
+            BatteryManager batteryManager = context.getSystemService(BatteryManager.class);
+            int currentLevel =
+                    batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
+            return Math.max((mBatteryLevelWhenServiceOn - currentLevel), 0);
+        } catch (NullPointerException e) {
+            loge("BatteryManager is null");
+            return 0;
+        }
+    }
+
+    /** Receives the battery status whether it is in charging or not, update interval is 60 sec. */
+    private final BroadcastReceiver mBatteryStatusReceiver = new BroadcastReceiver() {
+        private long mLastUpdatedTime = 0;
+        private static final long UPDATE_INTERVAL = 60 * 1000;
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            long currentTime = getCurrentTime();
+            if (currentTime - mLastUpdatedTime > UPDATE_INTERVAL) {
+                mLastUpdatedTime = currentTime;
+                int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
+                boolean isCharged = (status == BatteryManager.BATTERY_STATUS_CHARGING);
+                logd("Battery is charged(" + isCharged + ")");
+                updateSatelliteBatteryChargeTime(isCharged);
+            }
+        }
+    };
+
+    @VisibleForTesting
+    public boolean isSatelliteModemOn() {
+        return mIsSatelliteModemOn;
+    }
+
+    @VisibleForTesting
+    public long getCurrentTime() {
+        return System.currentTimeMillis();
+    }
+
+    private static void logd(@NonNull String log) {
+        if (DBG) {
+            Log.d(TAG, log);
+        }
+    }
+
+    private static void loge(@NonNull String log) {
+        Log.e(TAG, log);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/satellite/metrics/ProvisionMetricsStats.java b/src/java/com/android/internal/telephony/satellite/metrics/ProvisionMetricsStats.java
new file mode 100644
index 0000000..38696aa
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/metrics/ProvisionMetricsStats.java
@@ -0,0 +1,110 @@
+/*
+ * 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.satellite.metrics;
+
+import android.annotation.NonNull;
+import android.telephony.satellite.SatelliteManager;
+import android.util.Log;
+
+import com.android.internal.telephony.metrics.SatelliteStats;
+
+/**
+ * Stats to log to satellite metrics
+ */
+public class ProvisionMetricsStats {
+    private static final String TAG = ProvisionMetricsStats.class.getSimpleName();
+    private static final boolean DBG = false;
+
+    private static ProvisionMetricsStats sInstance = null;
+
+    public static final int INVALID_TIME = -1;
+
+    private int mResultCode;
+    private int mProvisioningStartTimeSec;
+    private boolean mIsProvisionRequest;
+    private boolean mIsCanceled;
+
+    private ProvisionMetricsStats() {
+        initializeProvisionParams();
+    }
+
+    /**
+     * Returns the Singleton instance of ProvisionMetricsStats class.
+     * If an instance of the Singleton class has not been created,
+     * it creates a new instance and returns it. Otherwise, it returns
+     * the existing instance.
+     * @return the Singleton instance of ProvisionMetricsStats
+     */
+    public static ProvisionMetricsStats getOrCreateInstance() {
+        if (sInstance == null) {
+            logd("Create new ProvisionMetricsStats.");
+            sInstance = new ProvisionMetricsStats();
+        }
+        return sInstance;
+    }
+
+    /** Sets the resultCode for provision metrics */
+    public ProvisionMetricsStats setResultCode(@SatelliteManager.SatelliteError int error) {
+        mResultCode = error;
+        return this;
+    }
+
+    /** Sets the start time of provisioning */
+    public void setProvisioningStartTime() {
+        mProvisioningStartTimeSec = (int) (System.currentTimeMillis() / 1000);
+    }
+
+    /** Sets the isProvisionRequest to indicate whether provision or de-provision */
+    public ProvisionMetricsStats setIsProvisionRequest(boolean isProvisionRequest) {
+        mIsProvisionRequest = isProvisionRequest;
+        return this;
+    }
+
+    /** Sets the isCanceled to know whether the provision is canceled */
+    public ProvisionMetricsStats setIsCanceled(boolean isCanceled) {
+        mIsCanceled = isCanceled;
+        return this;
+    }
+
+    /** Report the provision metrics atoms to PersistAtomsStorage in telephony */
+    public void reportProvisionMetrics() {
+        SatelliteStats.SatelliteProvisionParams provisionParams =
+                new SatelliteStats.SatelliteProvisionParams.Builder()
+                        .setResultCode(mResultCode)
+                        .setProvisioningTimeSec((int)
+                                (System.currentTimeMillis() / 1000) - mProvisioningStartTimeSec)
+                        .setIsProvisionRequest(mIsProvisionRequest)
+                        .setIsCanceled(mIsCanceled)
+                        .build();
+        SatelliteStats.getInstance().onSatelliteProvisionMetrics(provisionParams);
+        logd(provisionParams.toString());
+        initializeProvisionParams();
+    }
+
+    private void initializeProvisionParams() {
+        mResultCode = -1;
+        mProvisioningStartTimeSec = INVALID_TIME;
+        mIsProvisionRequest = false;
+        mIsCanceled = false;
+    }
+
+    private static void logd(@NonNull String log) {
+        if (DBG) {
+            Log.d(TAG, log);
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/satellite/metrics/SessionMetricsStats.java b/src/java/com/android/internal/telephony/satellite/metrics/SessionMetricsStats.java
new file mode 100644
index 0000000..776ba64
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/metrics/SessionMetricsStats.java
@@ -0,0 +1,97 @@
+/*
+ * 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.satellite.metrics;
+
+import android.annotation.NonNull;
+import android.telephony.satellite.SatelliteManager;
+import android.util.Log;
+
+import com.android.internal.telephony.metrics.SatelliteStats;
+
+/**
+ * Stats to log to satellite session metrics
+ */
+public class SessionMetricsStats {
+    private static final String TAG = SessionMetricsStats.class.getSimpleName();
+    private static final boolean DBG = false;
+
+    private static SessionMetricsStats sInstance = null;
+    private @SatelliteManager.SatelliteError int mInitializationResult;
+    private @SatelliteManager.NTRadioTechnology int mRadioTechnology;
+
+    private SessionMetricsStats() {
+        initializeSessionMetricsParam();
+    }
+
+    /**
+     * Returns the Singleton instance of SessionMetricsStats class.
+     * If an instance of the Singleton class has not been created,
+     * it creates a new instance and returns it. Otherwise, it returns
+     * the existing instance.
+     * @return the Singleton instance of SessionMetricsStats
+     */
+    public static SessionMetricsStats getInstance() {
+        if (sInstance == null) {
+            loge("create new SessionMetricsStats.");
+            sInstance = new SessionMetricsStats();
+        }
+        return sInstance;
+    }
+
+    /** Sets the satellite initialization result */
+    public SessionMetricsStats setInitializationResult(
+            @SatelliteManager.SatelliteError int result) {
+        logd("setInitializationResult(" + result + ")");
+        mInitializationResult = result;
+        return this;
+    }
+
+    /** Sets the satellite ratio technology */
+    public SessionMetricsStats setRadioTechnology(
+            @SatelliteManager.NTRadioTechnology int radioTechnology) {
+        logd("setRadioTechnology(" + radioTechnology + ")");
+        mRadioTechnology = radioTechnology;
+        return this;
+    }
+
+    /** Report the session metrics atoms to PersistAtomsStorage in telephony */
+    public void reportSessionMetrics() {
+        SatelliteStats.SatelliteSessionParams sessionParams =
+                new SatelliteStats.SatelliteSessionParams.Builder()
+                        .setSatelliteServiceInitializationResult(mInitializationResult)
+                        .setSatelliteTechnology(mRadioTechnology)
+                        .build();
+        logd(sessionParams.toString());
+        SatelliteStats.getInstance().onSatelliteSessionMetrics(sessionParams);
+        initializeSessionMetricsParam();
+    }
+
+    private void initializeSessionMetricsParam() {
+        mInitializationResult = SatelliteManager.SATELLITE_ERROR_NONE;
+        mRadioTechnology = SatelliteManager.NT_RADIO_TECHNOLOGY_UNKNOWN;
+    }
+
+    private static void logd(@NonNull String log) {
+        if (DBG) {
+            Log.d(TAG, log);
+        }
+    }
+
+    private static void loge(@NonNull String log) {
+        Log.e(TAG, log);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java b/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java
index 087aee6..b90dc5e 100644
--- a/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java
+++ b/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java
@@ -81,7 +81,7 @@
  * to the database should go through {@link SubscriptionManagerService}.
  */
 public class SubscriptionDatabaseManager extends Handler {
-    private static final String LOG_TAG = "SDM";
+    private static final String LOG_TAG = "SDMGR";
 
     /** Whether enabling verbose debugging message or not. */
     private static final boolean VDBG = false;
@@ -91,7 +91,6 @@
 
     /** The mapping from {@link SimInfo} table to {@link SubscriptionInfoInternal} get methods. */
     private static final Map<String, Function<SubscriptionInfoInternal, ?>>
-            // TODO: Support SimInfo.COLUMN_CB_XXX which are still used by wear.
             SUBSCRIPTION_GET_METHOD_MAP = Map.ofEntries(
             new AbstractMap.SimpleImmutableEntry<>(
                     SimInfo.COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID,
diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java b/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java
index d7b5725..b917698 100644
--- a/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java
+++ b/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java
@@ -1200,30 +1200,10 @@
                 .build();
     }
 
-    /**
-     * Get ID stripped PII information on user build.
-     *
-     * @param id The PII id.
-     *
-     * @return The stripped string.
-     */
-    public static String givePrintableId(String id) {
-        String idToPrint = null;
-        if (id != null) {
-            int len = id.length();
-            if (len > 6 && !TelephonyUtils.IS_DEBUGGABLE) {
-                idToPrint = id.substring(0, len - 6) + Rlog.pii(false, id.substring(len - 6));
-            } else {
-                idToPrint = id;
-            }
-        }
-        return idToPrint;
-    }
-
     @Override
     public String toString() {
         return "[SubscriptionInfoInternal: id=" + mId
-                + " iccId=" + givePrintableId(mIccId)
+                + " iccId=" + SubscriptionInfo.getPrintableId(mIccId)
                 + " simSlotIndex=" + mSimSlotIndex
                 + " portIndex=" + mPortIndex
                 + " isEmbedded=" + mIsEmbedded
@@ -1243,7 +1223,7 @@
                 + " mnc=" + mMnc
                 + " ehplmns=" + mEhplmns
                 + " hplmns=" + mHplmns
-                + " cardString=" + givePrintableId(mCardString)
+                + " cardString=" + SubscriptionInfo.getPrintableId(mCardString)
                 + " cardId=" + mCardId
                 + " nativeAccessRules=" + IccUtils.bytesToHexString(mNativeAccessRules)
                 + " carrierConfigAccessRules=" + IccUtils.bytesToHexString(
@@ -1261,7 +1241,7 @@
                 + " wifiCallingModeForRoaming="
                 + ImsMmTelManager.wifiCallingModeToString(mWifiCallingModeForRoaming)
                 + " enabledMobileDataPolicies=" + mEnabledMobileDataPolicies
-                + " imsi=" + givePrintableId(mImsi)
+                + " imsi=" + SubscriptionInfo.getPrintableId(mImsi)
                 + " rcsUceEnabled=" + mIsRcsUceEnabled
                 + " crossSimCallingEnabled=" + mIsCrossSimCallingEnabled
                 + " rcsConfig=" + IccUtils.bytesToHexString(mRcsConfig)
diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java b/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
index 2765ca4..c56ee04 100644
--- a/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
+++ b/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
@@ -46,6 +46,7 @@
 import android.os.RemoteException;
 import android.os.TelephonyServiceManager;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.provider.Telephony.SimInfo;
 import android.service.carrier.CarrierIdentifier;
@@ -90,7 +91,6 @@
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.ProxyController;
 import com.android.internal.telephony.RILConstants;
-import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.TelephonyPermissions;
 import com.android.internal.telephony.data.PhoneSwitcher;
@@ -548,7 +548,6 @@
         });
 
         SubscriptionManager.invalidateSubscriptionManagerServiceCaches();
-        SubscriptionManager.invalidateSubscriptionManagerServiceEnabledCaches();
 
         mContext.registerReceiver(new BroadcastReceiver() {
             @Override
@@ -997,8 +996,7 @@
 
         int subId = mSubscriptionDatabaseManager.insertSubscriptionInfo(builder.build());
         logl("insertSubscriptionInfo: Inserted a new subscription. subId=" + subId
-                + ", slotIndex=" + slotIndex + ", iccId="
-                + SubscriptionInfo.givePrintableIccid(iccId)
+                + ", slotIndex=" + slotIndex + ", iccId=" + SubscriptionInfo.getPrintableId(iccId)
                 + ", displayName=" + displayName + ", type="
                 + SubscriptionManager.subscriptionTypeToString(subscriptionType));
         return subId;
@@ -1089,9 +1087,11 @@
                     builder.setRemovableEmbedded(isRemovable);
 
                     // override DISPLAY_NAME if the priority of existing nameSource is <= carrier
-                    if (getNameSourcePriority(nameSource) <= getNameSourcePriority(
-                            SubscriptionManager.NAME_SOURCE_CARRIER)) {
-                        builder.setDisplayName(embeddedProfile.getNickname());
+                    String nickName = embeddedProfile.getNickname();
+                    if (nickName != null
+                            && getNameSourcePriority(nameSource) <= getNameSourcePriority(
+                                    SubscriptionManager.NAME_SOURCE_CARRIER)) {
+                        builder.setDisplayName(nickName);
                         builder.setDisplayNameSource(SubscriptionManager.NAME_SOURCE_CARRIER);
                     }
                     builder.setProfileClass(embeddedProfile.getProfileClass());
@@ -1159,11 +1159,14 @@
             } else {
                 loge("The eSIM profiles update was not successful.");
             }
+            log("updateEmbeddedSubscriptions: Finished embedded subscription update.");
+            // The runnable will be executed in the main thread. Pre Android-U behavior.
+            mHandler.post(() -> {
+                if (callback != null) {
+                    callback.run();
+                }
+            });
         });
-        log("updateEmbeddedSubscriptions: Finished embedded subscription update.");
-        if (callback != null) {
-            callback.run();
-        }
     }
 
     /**
@@ -1274,7 +1277,7 @@
                 + TelephonyManager.simStateToString(simState));
         for (UiccSlot slot : mUiccController.getUiccSlots()) {
             if (slot != null) {
-                log("  " + slot.toString());
+                log("  " + slot);
             }
         }
 
@@ -1309,16 +1312,13 @@
                 log("updateSubscription: SIM_STATE_NOT_READY is not a final state. Will update "
                         + "subscription later.");
                 return;
-            }
-
-            if (!areUiccAppsEnabledOnCard(phoneId)) {
+            } else {
                 logl("updateSubscription: UICC app disabled on slot " + phoneId);
                 markSubscriptionsInactive(phoneId);
             }
         } else {
-
             String iccId = getIccId(phoneId);
-            log("updateSubscription: Found iccId=" + SubscriptionInfo.givePrintableIccid(iccId)
+            log("updateSubscription: Found iccId=" + SubscriptionInfo.getPrintableId(iccId)
                     + " on phone " + phoneId);
 
             // For eSIM switching, SIM absent will not happen. Below is to exam if we find ICCID
@@ -1953,7 +1953,6 @@
      * @see SubscriptionManager#requestEmbeddedSubscriptionInfoListRefresh
      */
     @Override
-    // TODO: Remove this after SubscriptionController is removed.
     public void requestEmbeddedSubscriptionInfoListRefresh(int cardId) {
         updateEmbeddedSubscriptions(List.of(cardId), null);
     }
@@ -1975,7 +1974,7 @@
     public int addSubInfo(@NonNull String iccId, @NonNull String displayName, int slotIndex,
             @SubscriptionType int subscriptionType) {
         enforcePermissions("addSubInfo", Manifest.permission.MODIFY_PHONE_STATE);
-        logl("addSubInfo: iccId=" + SubscriptionInfo.givePrintableIccid(iccId) + ", slotIndex="
+        logl("addSubInfo: iccId=" + SubscriptionInfo.getPrintableId(iccId) + ", slotIndex="
                 + slotIndex + ", displayName=" + displayName + ", type="
                 + SubscriptionManager.subscriptionTypeToString(subscriptionType) + ", "
                 + getCallingPackage());
@@ -2022,18 +2021,17 @@
      * subscription type.
      * @param subscriptionType the type of subscription to be removed.
      *
-     * // TODO: Remove this terrible return value once SubscriptionController is removed.
-     * @return 0 if success, < 0 on error.
+     * @return {@code true} if succeeded, otherwise {@code false}.
      *
      * @throws NullPointerException if {@code uniqueId} is {@code null}.
      * @throws SecurityException if callers do not hold the required permission.
      */
     @Override
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
-    public int removeSubInfo(@NonNull String uniqueId, int subscriptionType) {
+    public boolean removeSubInfo(@NonNull String uniqueId, int subscriptionType) {
         enforcePermissions("removeSubInfo", Manifest.permission.MODIFY_PHONE_STATE);
 
-        logl("removeSubInfo: uniqueId=" + SubscriptionInfo.givePrintableIccid(uniqueId) + ", "
+        logl("removeSubInfo: uniqueId=" + SubscriptionInfo.getPrintableId(uniqueId) + ", "
                 + SubscriptionManager.subscriptionTypeToString(subscriptionType) + ", "
                 + getCallingPackage());
         final long identity = Binder.clearCallingIdentity();
@@ -2042,15 +2040,15 @@
                     .getSubscriptionInfoInternalByIccId(uniqueId);
             if (subInfo == null) {
                 loge("Cannot find subscription with uniqueId " + uniqueId);
-                return -1;
+                return false;
             }
             if (subInfo.getSubscriptionType() != subscriptionType) {
                 loge("The subscription type does not match.");
-                return -1;
+                return false;
             }
             mSlotIndexToSubId.remove(subInfo.getSimSlotIndex());
             mSubscriptionDatabaseManager.removeSubscriptionInfo(subInfo.getSubscriptionId());
-            return 0;
+            return true;
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -2956,9 +2954,6 @@
      * @param columnName Column name in the database. Note not all fields are supported.
      * @param value Value to store in the database.
      *
-     * // TODO: Remove return value after SubscriptionController is deleted.
-     * @return always 1
-     *
      * @throws IllegalArgumentException if {@code subscriptionId} is invalid, or the field is not
      * exposed.
      * @throws SecurityException if callers do not hold the required permission.
@@ -2968,7 +2963,7 @@
      */
     @Override
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
-    public int setSubscriptionProperty(int subId, @NonNull String columnName,
+    public void setSubscriptionProperty(int subId, @NonNull String columnName,
             @NonNull String value) {
         enforcePermissions("setSubscriptionProperty", Manifest.permission.MODIFY_PHONE_STATE);
 
@@ -2989,7 +2984,6 @@
             }
 
             mSubscriptionDatabaseManager.setSubscriptionProperty(subId, columnName, value);
-            return 1;
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -3579,12 +3573,6 @@
     public UserHandle getSubscriptionUserHandle(int subId) {
         enforcePermissions("getSubscriptionUserHandle",
                 Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION);
-
-        if (!mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_enable_get_subscription_user_handle)) {
-            return null;
-        }
-
         long token = Binder.clearCallingIdentity();
         try {
             SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
@@ -3595,6 +3583,7 @@
             }
 
             UserHandle userHandle = UserHandle.of(subInfo.getUserId());
+            logv("getSubscriptionUserHandle subId = " + subId + " userHandle = " + userHandle);
             if (userHandle.getIdentifier() == UserHandle.USER_NULL) {
                 return null;
             }
@@ -3614,7 +3603,6 @@
      * else {@code false} if subscription is not associated with user.
      *
      * @throws SecurityException if the caller doesn't have permissions required.
-     * @throws IllegalStateException if subscription service is not available.
      *
      */
     @Override
@@ -3632,6 +3620,13 @@
                 return true;
             }
 
+            List<Integer> subIdList = subInfoList.stream().map(SubscriptionInfo::getSubscriptionId)
+                    .collect(Collectors.toList());
+            if (!subIdList.contains(subscriptionId)) {
+                // Return true as this subscription is not available on the device.
+                return true;
+            }
+
             // Get list of subscriptions associated with this user.
             List<SubscriptionInfo> associatedSubscriptionsList =
                     getSubscriptionInfoListAssociatedWithUser(userHandle);
@@ -3661,7 +3656,6 @@
      * @return list of subscriptionInfo associated with the user.
      *
      * @throws SecurityException if the caller doesn't have permissions required.
-     * @throws IllegalStateException if subscription service is not available.
      *
      */
     @Override
@@ -3692,6 +3686,15 @@
                 }
             }
 
+            UserManager userManager = mContext.getSystemService(UserManager.class);
+            if ((userManager != null)
+                    && (userManager.isManagedProfile(userHandle.getIdentifier()))) {
+                // For work profile, return subscriptions associated only with work profile
+                return subscriptionsAssociatedWithUser;
+            }
+
+            // For all other profiles, if subscriptionsAssociatedWithUser is empty return all the
+            // subscriptionsWithNoAssociation.
             return subscriptionsAssociatedWithUser.isEmpty() ?
                     subscriptionsWithNoAssociation : subscriptionsAssociatedWithUser;
         } finally {
@@ -3700,16 +3703,6 @@
     }
 
     /**
-     * @return {@code true} if using {@link SubscriptionManagerService} instead of
-     * {@link SubscriptionController}.
-     */
-    //TODO: Removed before U AOSP public release.
-    @Override
-    public boolean isSubscriptionManagerServiceEnabled() {
-        return true;
-    }
-
-    /**
      * Called during setup wizard restore flow to attempt to restore the backed up sim-specific
      * configs to device for all existing SIMs in the subscription database {@link SimInfo}.
      * Internally, it will store the backup data in an internal file. This file will persist on
@@ -3817,7 +3810,7 @@
     public void updateSimStateForInactivePort(int slotIndex, @NonNull String iccId) {
         mHandler.post(() -> {
             logl("updateSimStateForInactivePort: slotIndex=" + slotIndex + ", iccId="
-                    + SubscriptionInfo.givePrintableIccid(iccId));
+                    + SubscriptionInfo.getPrintableId(iccId));
             if (mSlotIndexToSubId.containsKey(slotIndex)) {
                 // Re-enable the UICC application , so it will be in enabled state when it becomes
                 // active again. (Pre-U behavior)
@@ -3973,6 +3966,15 @@
     }
 
     /**
+     * Log verbose messages.
+     *
+     * @param s verbose messages
+     */
+    private void logv(@NonNull String s) {
+        Rlog.v(LOG_TAG, s);
+    }
+
+    /**
      * Dump the state of {@link SubscriptionManagerService}.
      *
      * @param fd File descriptor
@@ -3981,79 +3983,87 @@
      */
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter printWriter,
             @NonNull String[] args) {
-        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
-        pw.println(SubscriptionManagerService.class.getSimpleName() + ":");
-        pw.println("Active modem count=" + mTelephonyManager.getActiveModemCount());
-        pw.println("Logical SIM slot sub id mapping:");
-        pw.increaseIndent();
-        mSlotIndexToSubId.forEach((slotIndex, subId)
-                -> pw.println("Logical SIM slot " + slotIndex + ": subId=" + subId));
-        pw.decreaseIndent();
-        pw.println("ICCID:");
-        pw.increaseIndent();
-        for (int i = 0; i < mTelephonyManager.getActiveModemCount(); i++) {
-            pw.println("slot " + i + ": " + getIccId(i));
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP,
+                "Requires android.Manifest.permission.DUMP");
+        final long token = Binder.clearCallingIdentity();
+        try {
+            IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
+            pw.println(SubscriptionManagerService.class.getSimpleName() + ":");
+            pw.println("Active modem count=" + mTelephonyManager.getActiveModemCount());
+            pw.println("Logical SIM slot sub id mapping:");
+            pw.increaseIndent();
+            mSlotIndexToSubId.forEach((slotIndex, subId)
+                    -> pw.println("Logical SIM slot " + slotIndex + ": subId=" + subId));
+            pw.decreaseIndent();
+            pw.println("ICCID:");
+            pw.increaseIndent();
+            for (int i = 0; i < mTelephonyManager.getActiveModemCount(); i++) {
+                pw.println("slot " + i + ": " + SubscriptionInfo.getPrintableId(getIccId(i)));
+            }
+            pw.decreaseIndent();
+            pw.println();
+            pw.println("defaultSubId=" + getDefaultSubId());
+            pw.println("defaultVoiceSubId=" + getDefaultVoiceSubId());
+            pw.println("defaultDataSubId=" + getDefaultDataSubId());
+            pw.println("activeDataSubId=" + getActiveDataSubscriptionId());
+            pw.println("defaultSmsSubId=" + getDefaultSmsSubId());
+            pw.println("areAllSubscriptionsLoaded=" + areAllSubscriptionsLoaded());
+            pw.println();
+            for (int i = 0; i < mSimState.length; i++) {
+                pw.println("mSimState[" + i + "]="
+                        + TelephonyManager.simStateToString(mSimState[i]));
+            }
+
+            pw.println();
+            pw.println("Active subscriptions:");
+            pw.increaseIndent();
+            mSubscriptionDatabaseManager.getAllSubscriptions().stream()
+                    .filter(SubscriptionInfoInternal::isActive).forEach(pw::println);
+            pw.decreaseIndent();
+
+            pw.println();
+            pw.println("All subscriptions:");
+            pw.increaseIndent();
+            mSubscriptionDatabaseManager.getAllSubscriptions().forEach(pw::println);
+            pw.decreaseIndent();
+            pw.println();
+
+            pw.print("Embedded subscriptions: [");
+            pw.println(mSubscriptionDatabaseManager.getAllSubscriptions().stream()
+                    .filter(SubscriptionInfoInternal::isEmbedded)
+                    .map(subInfo -> String.valueOf(subInfo.getSubscriptionId()))
+                    .collect(Collectors.joining(", ")) + "]");
+
+            pw.print("Opportunistic subscriptions: [");
+            pw.println(mSubscriptionDatabaseManager.getAllSubscriptions().stream()
+                    .filter(SubscriptionInfoInternal::isOpportunistic)
+                    .map(subInfo -> String.valueOf(subInfo.getSubscriptionId()))
+                    .collect(Collectors.joining(", ")) + "]");
+
+            pw.print("getAvailableSubscriptionInfoList: [");
+            pw.println(getAvailableSubscriptionInfoList(
+                    mContext.getOpPackageName(), mContext.getFeatureId()).stream()
+                    .map(subInfo -> String.valueOf(subInfo.getSubscriptionId()))
+                    .collect(Collectors.joining(", ")) + "]");
+
+            pw.print("getSelectableSubscriptionInfoList: [");
+            pw.println(mSubscriptionManager.getSelectableSubscriptionInfoList().stream()
+                    .map(subInfo -> String.valueOf(subInfo.getSubscriptionId()))
+                    .collect(Collectors.joining(", ")) + "]");
+
+            if (mEuiccManager != null) {
+                pw.println("Euicc enabled=" + mEuiccManager.isEnabled());
+            }
+            pw.println();
+            pw.println("Local log:");
+            pw.increaseIndent();
+            mLocalLog.dump(fd, pw, args);
+            pw.decreaseIndent();
+            pw.decreaseIndent();
+            pw.println();
+            mSubscriptionDatabaseManager.dump(fd, pw, args);
+        } finally {
+            Binder.restoreCallingIdentity(token);
         }
-        pw.decreaseIndent();
-        pw.println();
-        pw.println("defaultSubId=" + getDefaultSubId());
-        pw.println("defaultVoiceSubId=" + getDefaultVoiceSubId());
-        pw.println("defaultDataSubId=" + getDefaultDataSubId());
-        pw.println("activeDataSubId=" + getActiveDataSubscriptionId());
-        pw.println("defaultSmsSubId=" + getDefaultSmsSubId());
-        pw.println("areAllSubscriptionsLoaded=" + areAllSubscriptionsLoaded());
-        pw.println();
-        for (int i = 0; i < mSimState.length; i++) {
-            pw.println("mSimState[" + i + "]=" + TelephonyManager.simStateToString(mSimState[i]));
-        }
-
-        pw.println();
-        pw.println("Active subscriptions:");
-        pw.increaseIndent();
-        mSubscriptionDatabaseManager.getAllSubscriptions().stream()
-                .filter(SubscriptionInfoInternal::isActive).forEach(pw::println);
-        pw.decreaseIndent();
-
-        pw.println();
-        pw.println("All subscriptions:");
-        pw.increaseIndent();
-        mSubscriptionDatabaseManager.getAllSubscriptions().forEach(pw::println);
-        pw.decreaseIndent();
-        pw.println();
-
-        pw.print("Embedded subscriptions: [");
-        pw.println(mSubscriptionDatabaseManager.getAllSubscriptions().stream()
-                .filter(SubscriptionInfoInternal::isEmbedded)
-                .map(subInfo -> String.valueOf(subInfo.getSubscriptionId()))
-                .collect(Collectors.joining(", ")) + "]");
-
-        pw.print("Opportunistic subscriptions: [");
-        pw.println(mSubscriptionDatabaseManager.getAllSubscriptions().stream()
-                .filter(SubscriptionInfoInternal::isOpportunistic)
-                .map(subInfo -> String.valueOf(subInfo.getSubscriptionId()))
-                .collect(Collectors.joining(", ")) + "]");
-
-        pw.print("getAvailableSubscriptionInfoList: [");
-        pw.println(getAvailableSubscriptionInfoList(
-                mContext.getOpPackageName(), mContext.getFeatureId()).stream()
-                .map(subInfo -> String.valueOf(subInfo.getSubscriptionId()))
-                .collect(Collectors.joining(", ")) + "]");
-
-        pw.print("getSelectableSubscriptionInfoList: [");
-        pw.println(mSubscriptionManager.getSelectableSubscriptionInfoList().stream()
-                .map(subInfo -> String.valueOf(subInfo.getSubscriptionId()))
-                .collect(Collectors.joining(", ")) + "]");
-
-        if (mEuiccManager != null) {
-            pw.println("Euicc enabled=" + mEuiccManager.isEnabled());
-        }
-        pw.println();
-        pw.println("Local log:");
-        pw.increaseIndent();
-        mLocalLog.dump(fd, pw, args);
-        pw.decreaseIndent();
-        pw.decreaseIndent();
-        pw.println();
-        mSubscriptionDatabaseManager.dump(fd, pw, args);
     }
 }
diff --git a/src/java/com/android/internal/telephony/uicc/AdnRecordCache.java b/src/java/com/android/internal/telephony/uicc/AdnRecordCache.java
index 21a6e37..90c9491 100644
--- a/src/java/com/android/internal/telephony/uicc/AdnRecordCache.java
+++ b/src/java/com/android/internal/telephony/uicc/AdnRecordCache.java
@@ -23,10 +23,12 @@
 import android.os.Message;
 import android.util.SparseArray;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.gsm.UsimPhoneBookManager;
 
 import java.util.ArrayList;
 import java.util.Iterator;
+import java.util.Locale;
 
 /**
  * {@hide}
@@ -58,14 +60,16 @@
     static final int EVENT_UPDATE_ADN_DONE = 2;
 
     //***** Constructor
-
-
-
     AdnRecordCache(IccFileHandler fh) {
         mFh = fh;
         mUsimPhoneBookManager = new UsimPhoneBookManager(mFh, this);
     }
 
+    public AdnRecordCache(IccFileHandler fh, UsimPhoneBookManager usimPhoneBookManager) {
+        mFh = fh;
+        mUsimPhoneBookManager = usimPhoneBookManager;
+    }
+
     //***** Called from SIMRecords
 
     /**
@@ -156,14 +160,14 @@
         int extensionEF = extensionEfForEf(efid);
         if (extensionEF < 0) {
             sendErrorResponse(response, "EF is not known ADN-like EF:0x" +
-                    Integer.toHexString(efid).toUpperCase());
+                    Integer.toHexString(efid).toUpperCase(Locale.ROOT));
             return;
         }
 
         Message pendingResponse = mUserWriteResponse.get(efid);
         if (pendingResponse != null) {
             sendErrorResponse(response, "Have pending update for EF:0x" +
-                    Integer.toHexString(efid).toUpperCase());
+                    Integer.toHexString(efid).toUpperCase(Locale.ROOT));
             return;
         }
 
@@ -190,16 +194,14 @@
      */
     public void updateAdnBySearch(int efid, AdnRecord oldAdn, AdnRecord newAdn,
             String pin2, Message response) {
-
         int extensionEF;
         extensionEF = extensionEfForEf(efid);
 
         if (extensionEF < 0) {
             sendErrorResponse(response, "EF is not known ADN-like EF:0x" +
-                    Integer.toHexString(efid).toUpperCase());
+                    Integer.toHexString(efid).toUpperCase(Locale.ROOT));
             return;
         }
-
         ArrayList<AdnRecord>  oldAdnList;
 
         if (efid == EF_PBR) {
@@ -207,13 +209,11 @@
         } else {
             oldAdnList = getRecordsIfLoaded(efid);
         }
-
         if (oldAdnList == null) {
             sendErrorResponse(response, "Adn list not exist for EF:0x" +
-                    Integer.toHexString(efid).toUpperCase());
+                    Integer.toHexString(efid).toUpperCase(Locale.ROOT));
             return;
         }
-
         int index = -1;
         int count = 1;
         for (Iterator<AdnRecord> it = oldAdnList.iterator(); it.hasNext(); ) {
@@ -223,7 +223,6 @@
             }
             count++;
         }
-
         if (index == -1) {
             sendErrorResponse(response, "Adn record don't exist for " + oldAdn);
             return;
@@ -244,7 +243,7 @@
 
         if (pendingResponse != null) {
             sendErrorResponse(response, "Have pending update for EF:0x" +
-                    Integer.toHexString(efid).toUpperCase());
+                    Integer.toHexString(efid).toUpperCase(Locale.ROOT));
             return;
         }
 
@@ -307,7 +306,7 @@
             if (response != null) {
                 AsyncResult.forMessage(response).exception
                     = new RuntimeException("EF is not known ADN-like EF:0x" +
-                        Integer.toHexString(efid).toUpperCase());
+                        Integer.toHexString(efid).toUpperCase(Locale.ROOT));
                 response.sendToTarget();
             }
 
@@ -342,7 +341,6 @@
     handleMessage(Message msg) {
         AsyncResult ar;
         int efid;
-
         switch(msg.what) {
             case EVENT_LOAD_ALL_ADN_LIKE_DONE:
                 /* arg1 is efid, obj.result is ArrayList<AdnRecord>*/
@@ -381,4 +379,24 @@
                 break;
         }
     }
+
+    @VisibleForTesting
+    protected void setAdnLikeWriters(int key, ArrayList<Message> waiters) {
+        mAdnLikeWaiters.put(EF_MBDN, waiters);
+    }
+
+    @VisibleForTesting
+    protected void setAdnLikeFiles(int key, ArrayList<AdnRecord> adnRecordList) {
+        mAdnLikeFiles.put(EF_MBDN, adnRecordList);
+    }
+
+    @VisibleForTesting
+    protected void setUserWriteResponse(int key, Message message) {
+        mUserWriteResponse.put(EF_MBDN, message);
+    }
+
+    @VisibleForTesting
+    protected UsimPhoneBookManager getUsimPhoneBookManager() {
+        return mUsimPhoneBookManager;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/uicc/AdnRecordLoader.java b/src/java/com/android/internal/telephony/uicc/AdnRecordLoader.java
index a688c6e..5b0a6a3 100644
--- a/src/java/com/android/internal/telephony/uicc/AdnRecordLoader.java
+++ b/src/java/com/android/internal/telephony/uicc/AdnRecordLoader.java
@@ -59,6 +59,7 @@
     static final int EVENT_EF_LINEAR_RECORD_SIZE_DONE = 4;
     static final int EVENT_UPDATE_RECORD_DONE = 5;
 
+    static final int VOICEMAIL_ALPHATAG_ARG = 1;
     //***** Constructor
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -177,14 +178,34 @@
                     data = adn.buildAdnString(recordSize[0]);
 
                     if(data == null) {
-                        throw new RuntimeException("wrong ADN format",
-                                ar.exception);
+                        /**
+                         * The voicemail number saving to the SIM is in name(alphaTag) and number
+                         * format. {@link recordSize[0]} indicates the SIM EF memory size that the
+                         * sim can have to save both voicemail name and number. 14 byte of memory
+                         * is reserved to save the voicemail number and remaining memory is reserved
+                         * for the alphaTag. In case if we receive the alphaTag which is more than
+                         * the reserved memory size then SIM will throw the exception and it don't
+                         * save both the voicemail number and alphaTag. To avoid this problem, in
+                         * case alphaTag length is more we nullify the alphaTag and save the same.
+                         */
+                        if (mUserResponse.arg1 == VOICEMAIL_ALPHATAG_ARG) {
+                            adn.mAlphaTag = null;
+                            data = adn.buildAdnString(recordSize[0]);
+                        }
+                        if (data == null) {
+                            throw new RuntimeException("wrong ADN format",
+                                    ar.exception);
+                        }
                     }
 
-
-                    mFh.updateEFLinearFixed(mEf, getEFPath(mEf), mRecordNumber,
-                            data, mPin2, obtainMessage(EVENT_UPDATE_RECORD_DONE));
-
+                    // Send adn record to caller to track the changes made to alphaTag
+                    if (mUserResponse.arg1 == VOICEMAIL_ALPHATAG_ARG) {
+                        mFh.updateEFLinearFixed(mEf, getEFPath(mEf), mRecordNumber,
+                                data, mPin2, obtainMessage(EVENT_UPDATE_RECORD_DONE, adn));
+                    } else {
+                        mFh.updateEFLinearFixed(mEf, getEFPath(mEf), mRecordNumber,
+                                data, mPin2, obtainMessage(EVENT_UPDATE_RECORD_DONE));
+                    }
                     mPendingExtLoads = 1;
 
                     break;
@@ -195,7 +216,12 @@
                                 ar.exception);
                     }
                     mPendingExtLoads = 0;
-                    mResult = null;
+                    // send the adn record back to caller through the result of AsyncResult
+                    if (mUserResponse.arg1 == VOICEMAIL_ALPHATAG_ARG) {
+                        mResult =  ar.userObj;
+                    } else {
+                        mResult = null;
+                    }
                     break;
                 case EVENT_ADN_LOAD_DONE:
                     ar = (AsyncResult)(msg.obj);
diff --git a/src/java/com/android/internal/telephony/uicc/IccCardStatus.java b/src/java/com/android/internal/telephony/uicc/IccCardStatus.java
index ec07780..f0d949d 100644
--- a/src/java/com/android/internal/telephony/uicc/IccCardStatus.java
+++ b/src/java/com/android/internal/telephony/uicc/IccCardStatus.java
@@ -20,6 +20,7 @@
 import android.os.Build;
 import android.telephony.SubscriptionInfo;
 
+import com.android.internal.telephony.uicc.IccSlotStatus.MultipleEnabledProfilesMode;
 import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.telephony.Rlog;
 
@@ -90,6 +91,30 @@
 
     public IccSlotPortMapping mSlotPortMapping;
 
+    public MultipleEnabledProfilesMode mSupportedMepMode = MultipleEnabledProfilesMode.NONE;
+
+    /**
+     * Set the MultipleEnabledProfilesMode according to the input mode.
+     */
+    public void setMultipleEnabledProfilesMode(int mode) {
+        switch(mode) {
+            case 0:
+                mSupportedMepMode = MultipleEnabledProfilesMode.NONE;
+                break;
+            case 1:
+                mSupportedMepMode = MultipleEnabledProfilesMode.MEP_A1;
+                break;
+            case 2:
+                mSupportedMepMode = MultipleEnabledProfilesMode.MEP_A2;
+                break;
+            case 3:
+                mSupportedMepMode = MultipleEnabledProfilesMode.MEP_B;
+                break;
+            default:
+                throw new RuntimeException("Unrecognized RIL_MultipleEnabledProfilesMode: " + mode);
+        }
+    }
+
     public void setCardState(int state) {
         switch(state) {
         case 0:
@@ -172,8 +197,9 @@
         }
 
         sb.append(",atr=").append(atr);
-        sb.append(",iccid=").append(SubscriptionInfo.givePrintableIccid(iccid));
+        sb.append(",iccid=").append(SubscriptionInfo.getPrintableId(iccid));
         sb.append(",eid=").append(Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, eid));
+        sb.append(",SupportedMepMode=").append(mSupportedMepMode);
         sb.append(",SlotPortMapping=").append(mSlotPortMapping);
 
         sb.append("}");
diff --git a/src/java/com/android/internal/telephony/uicc/IccFileHandler.java b/src/java/com/android/internal/telephony/uicc/IccFileHandler.java
index 6c4ac69..5f8ccb4 100644
--- a/src/java/com/android/internal/telephony/uicc/IccFileHandler.java
+++ b/src/java/com/android/internal/telephony/uicc/IccFileHandler.java
@@ -22,6 +22,7 @@
 import android.os.Handler;
 import android.os.Message;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.CommandsInterface;
 
 import java.util.ArrayList;
@@ -108,7 +109,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     protected final String mAid;
 
-    static class LoadLinearFixedContext {
+    public static class LoadLinearFixedContext {
 
         int mEfid;
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -145,14 +146,11 @@
             mOnLoaded = onLoaded;
             mPath = path;
         }
+    }
 
-        LoadLinearFixedContext(int efid, Message onLoaded) {
-            mEfid = efid;
-            mRecordNum = 1;
-            mLoadAll = true;
-            mOnLoaded = onLoaded;
-            mPath = null;
-        }
+    @VisibleForTesting
+    public int getEfid(LoadLinearFixedContext lc) {
+        return lc.mEfid;
     }
 
     /**
@@ -164,6 +162,13 @@
         mCi = ci;
     }
 
+    @VisibleForTesting
+    public IccFileHandler(CommandsInterface ci) {
+        mParentApp = null;
+        mAid = null;
+        mCi = ci;
+    }
+
     public void dispose() {
     }
 
@@ -267,8 +272,7 @@
      * @param path Path of the EF on the card
      * @param onLoaded ((AsnyncResult)(onLoaded.obj)).result is the size of data int
      */
-    public void getEFTransparentRecordSize(int fileid, String path, Message onLoaded) {
-        String efPath = (path == null) ? getEFPath(fileid) : path;
+    public void getEFTransparentRecordSize(int fileid, Message onLoaded) {
         Message response = obtainMessage(EVENT_GET_EF_TRANSPARENT_SIZE_DONE, fileid, 0, onLoaded);
         mCi.iccIOForApp(
                 COMMAND_GET_RESPONSE,
@@ -284,16 +288,6 @@
     }
 
     /**
-     * Get record size for a transparent EF
-     *
-     * @param fileid EF id
-     * @param onLoaded ((AsnyncResult)(onLoaded.obj)).result is the size of the data int
-     */
-    public void getEFTransparentRecordSize(int fileid, Message onLoaded) {
-        getEFTransparentRecordSize(fileid, getEFPath(fileid), onLoaded);
-    }
-
-    /**
      * Load all records from a SIM Linear Fixed EF
      *
      * @param fileid EF id
diff --git a/src/java/com/android/internal/telephony/uicc/IccRecords.java b/src/java/com/android/internal/telephony/uicc/IccRecords.java
index da112b1..f803696 100644
--- a/src/java/com/android/internal/telephony/uicc/IccRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/IccRecords.java
@@ -257,6 +257,8 @@
     // call back received on this upon EF_SMSS record update.
     public static final int EVENT_SET_SMSS_RECORD_DONE = 201;
 
+    private static final int EVENT_GET_FDN_DONE = 202;
+
     /**
      * There are two purposes for this class. First, each instance of AuthAsyncResponse acts as a
      * lock to for calling thead to wait in getIccSimChallengeResponse(). Second, pass the IMS
@@ -269,7 +271,7 @@
 
     @Override
     public String toString() {
-        String iccIdToPrint = SubscriptionInfo.givePrintableIccid(mFullIccId);
+        String iccIdToPrint = SubscriptionInfo.getPrintableId(mFullIccId);
         return "mDestroyed=" + mDestroyed
                 + " mContext=" + mContext
                 + " mCi=" + mCi
@@ -999,6 +1001,15 @@
                 }
                 break;
 
+            case EVENT_GET_FDN_DONE:
+                ar = (AsyncResult) msg.obj;
+                if (ar.exception != null) {
+                    loge("Failed to read USIM EF_FDN field error=" + ar.exception);
+                } else {
+                    log("EF_FDN read successfully");
+                }
+                break;
+
             default:
                 super.handleMessage(msg);
         }
@@ -1428,7 +1439,7 @@
         pw.println(" mRecordsToLoad=" + mRecordsToLoad);
         pw.println(" mRdnCache=" + mAdnCache);
 
-        String iccIdToPrint = SubscriptionInfo.givePrintableIccid(mFullIccId);
+        String iccIdToPrint = SubscriptionInfo.getPrintableId(mFullIccId);
         pw.println(" iccid=" + iccIdToPrint);
         pw.println(" mMsisdn=" + Rlog.pii(VDBG, mMsisdn));
         pw.println(" mMsisdnTag=" + mMsisdnTag);
@@ -1674,4 +1685,12 @@
             return mMsg;
         }
     }
+
+    public void loadFdnRecords() {
+        if (mParentApp != null) {
+            log("Loading FdnRecords");
+            mAdnCache.requestLoadAllAdnLike(IccConstants.EF_FDN, EF_EXT2,
+                    obtainMessage(EVENT_GET_FDN_DONE));
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/uicc/IccSimPortInfo.java b/src/java/com/android/internal/telephony/uicc/IccSimPortInfo.java
index 9a5e10d..4164a1e 100644
--- a/src/java/com/android/internal/telephony/uicc/IccSimPortInfo.java
+++ b/src/java/com/android/internal/telephony/uicc/IccSimPortInfo.java
@@ -51,7 +51,7 @@
     public String toString() {
         StringBuilder sb = new StringBuilder();
         sb.append("{").append("iccid=")
-                .append(SubscriptionInfo.givePrintableIccid(mIccId)).append(",")
+                .append(SubscriptionInfo.getPrintableId(mIccId)).append(",")
                 .append("logicalSlotIndex=").append(mLogicalSlotIndex).append(",")
                 .append("portActive=").append(mPortActive)
                 .append("}");
diff --git a/src/java/com/android/internal/telephony/uicc/IccSlotStatus.java b/src/java/com/android/internal/telephony/uicc/IccSlotStatus.java
index 96a3a33..8e55f7c 100644
--- a/src/java/com/android/internal/telephony/uicc/IccSlotStatus.java
+++ b/src/java/com/android/internal/telephony/uicc/IccSlotStatus.java
@@ -30,11 +30,46 @@
     /* Added state active to check slotState in old HAL case.*/
     public static final int STATE_ACTIVE = 1;
 
+    public enum MultipleEnabledProfilesMode {
+        /**
+         * If there is no jointly supported MEP mode, set supported MEP mode to NONE.
+         */
+        NONE,
+        /**
+         * In case of MEP-A1, the ISD-R is selected on eSIM port 0 only and profiles are selected
+         * on eSIM ports 1 and higher, with the eSIM port being assigned by the LPA or platform.
+         */
+        MEP_A1,
+        /**
+         * In case of MEP-A2, the ISD-R is selected on eSIM port 0 only and profiles are selected
+         * on eSIM ports 1 and higher, with the eSIM port being assigned by the eUICC.
+         */
+        MEP_A2,
+        /**
+         * In case of MEP-B, profiles are selected on eSIM ports 0 and higher, with the ISD-R being
+         * selectable on any of these eSIM ports.
+         */
+        MEP_B;
+
+        public boolean isMepAMode() {
+            return (this == MEP_A1 || this == MEP_A2);
+        }
+
+        public boolean isMepA1Mode() {
+            return this == MEP_A1;
+        }
+
+        public boolean isMepMode() {
+            return this != NONE;
+        }
+    }
+
     public IccCardStatus.CardState  cardState;
     public String     atr;
     public String     eid;
 
     public IccSimPortInfo[] mSimPortInfos;
+    public MultipleEnabledProfilesMode mSupportedMepMode = MultipleEnabledProfilesMode.NONE;
 
     /**
      * Set the cardState according to the input state.
@@ -58,6 +93,28 @@
         }
     }
 
+    /**
+     * Set the MultipleEnabledProfilesMode according to the input mode.
+     */
+    public void setMultipleEnabledProfilesMode(int mode) {
+        switch(mode) {
+            case 0:
+                mSupportedMepMode = MultipleEnabledProfilesMode.NONE;
+                break;
+            case 1:
+                mSupportedMepMode = MultipleEnabledProfilesMode.MEP_A1;
+                break;
+            case 2:
+                mSupportedMepMode = MultipleEnabledProfilesMode.MEP_A2;
+                break;
+            case 3:
+                mSupportedMepMode = MultipleEnabledProfilesMode.MEP_B;
+                break;
+            default:
+                throw new RuntimeException("Unrecognized RIL_MultipleEnabledProfilesMode: " + mode);
+        }
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
@@ -72,6 +129,7 @@
         } else {
             sb.append("num_ports=null");
         }
+        sb.append(", SupportedMepMode=" + mSupportedMepMode);
         sb.append("}");
         return sb.toString();
     }
diff --git a/src/java/com/android/internal/telephony/uicc/InstallCarrierAppUtils.java b/src/java/com/android/internal/telephony/uicc/InstallCarrierAppUtils.java
index 412295d..7666f4c 100644
--- a/src/java/com/android/internal/telephony/uicc/InstallCarrierAppUtils.java
+++ b/src/java/com/android/internal/telephony/uicc/InstallCarrierAppUtils.java
@@ -38,6 +38,7 @@
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.Locale;
 
 /**
  * Utility methods for installing the carrier app when a SIM is insterted without the carrier app
@@ -178,7 +179,7 @@
      */
     @VisibleForTesting
     public static String getAppNameFromPackageName(String packageName, String mapString) {
-        packageName = packageName.toLowerCase();
+        packageName = packageName.toLowerCase(Locale.ROOT);
         final String pairDelim = "\\s*;\\s*";
         final String keyValueDelim = "\\s*:\\s*";
 
diff --git a/src/java/com/android/internal/telephony/uicc/IsimServiceTable.java b/src/java/com/android/internal/telephony/uicc/IsimServiceTable.java
new file mode 100644
index 0000000..289229c
--- /dev/null
+++ b/src/java/com/android/internal/telephony/uicc/IsimServiceTable.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 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.uicc;
+
+public final class IsimServiceTable extends IccServiceTable {
+    private static final String TAG = "IsimServiceTable";
+    public enum IsimService {
+        PCSCF_ADDRESS,
+        GBA,                                // Generic Bootstrapping Architecture (GBA)
+        HTTP_DIGEST,
+        GBA_LOCALKEY_ESTABLISHMENT,         // GBA-based Local Key Establishment Mechanism
+        PCSCF_DISCOVERY_FOR_IMS,            // Support of P-CSCF discovery for IMS Local Break Out
+        SMS,
+        SMSR,                               // Short Message Status Reports
+        SM_OVERIP_AND_DATA_DL_VIA_SMS_PP,   // Support for SM-over-IP including data download via
+        // SMS-PP
+        COMMUNICATION_CONTROL_FOR_IMS_BY_ISIM,
+        UICC_ACCESS_TO_IMS
+    }
+
+    public IsimServiceTable(byte[] table) {
+        super(table);
+    }
+
+    public boolean isAvailable(IsimService service) {
+        return super.isAvailable(service.ordinal());
+    }
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+
+    @Override
+    protected Object[] getValues() {
+        return IsimService.values();
+    }
+
+    public byte[] getISIMServiceTable() {
+        return mServiceTable;
+    }
+}
diff --git a/src/java/com/android/internal/telephony/uicc/IsimUiccRecords.java b/src/java/com/android/internal/telephony/uicc/IsimUiccRecords.java
index f9b51cf..9591a498 100644
--- a/src/java/com/android/internal/telephony/uicc/IsimUiccRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/IsimUiccRecords.java
@@ -225,6 +225,11 @@
         }
     }
 
+    @VisibleForTesting
+    public EfIsimIstLoaded getIsimIstObject() {
+        return new EfIsimIstLoaded();
+    }
+
     private class EfIsimSmssLoaded implements IccRecords.IccRecordLoaded {
 
         @Override
@@ -484,11 +489,11 @@
         pw.println("IsimRecords: " + this);
         pw.println(" extends:");
         super.dump(fd, pw, args);
+        pw.println(" mIsimServiceTable=" + getIsimServiceTable());
         if (DUMP_RECORDS) {
             pw.println(" mIsimImpi=" + mIsimImpi);
             pw.println(" mIsimDomain=" + mIsimDomain);
             pw.println(" mIsimImpu[]=" + Arrays.toString(mIsimImpu));
-            pw.println(" mIsimIst" + mIsimIst);
             pw.println(" mIsimPcscf" + Arrays.toString(mIsimPcscf));
             pw.println(" mPsismsc=" + mPsiSmsc);
             pw.println(" mSmss TPMR=" + getSmssTpmrValue());
@@ -496,6 +501,14 @@
         pw.flush();
     }
 
+    // Just to return the Enums of service table to print in DUMP
+    private IsimServiceTable getIsimServiceTable() {
+        if (mIsimIst != null) {
+            byte[] istTable = IccUtils.hexStringToBytes(mIsimIst);
+            return new IsimServiceTable(istTable);
+        }
+        return null;
+    }
     @Override
     public int getVoiceMessageCount() {
         return 0; // Not applicable to Isim
diff --git a/src/java/com/android/internal/telephony/uicc/PinStorage.java b/src/java/com/android/internal/telephony/uicc/PinStorage.java
index 8623f0a..23769ad 100644
--- a/src/java/com/android/internal/telephony/uicc/PinStorage.java
+++ b/src/java/com/android/internal/telephony/uicc/PinStorage.java
@@ -28,6 +28,7 @@
 import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT__EVENT__PIN_COUNT_NOT_MATCHING_AFTER_REBOOT;
 import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT__EVENT__PIN_DECRYPTION_ERROR;
 import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT__EVENT__PIN_ENCRYPTION_ERROR;
+import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT__EVENT__PIN_ENCRYPTION_KEY_MISSING;
 import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT__EVENT__PIN_REQUIRED_AFTER_REBOOT;
 import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT__EVENT__PIN_STORED_FOR_VERIFICATION;
 import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT__EVENT__PIN_VERIFICATION_FAILURE;
@@ -722,7 +723,11 @@
      */
     @Nullable
     private StoredPin decryptStoredPin(byte[] blob, @Nullable SecretKey secretKey) {
-        if (secretKey != null) {
+        if (secretKey == null) {
+            TelephonyStatsLog.write(PIN_STORAGE_EVENT,
+                    PIN_STORAGE_EVENT__EVENT__PIN_ENCRYPTION_KEY_MISSING,
+                    /* number_of_pins= */ 1, /* package_name= */ "");
+        } else {
             try {
                 byte[] decryptedPin = decrypt(secretKey, blob);
                 if (decryptedPin.length > 0) {
diff --git a/src/java/com/android/internal/telephony/uicc/PlmnActRecord.java b/src/java/com/android/internal/telephony/uicc/PlmnActRecord.java
old mode 100755
new mode 100644
diff --git a/src/java/com/android/internal/telephony/uicc/PortUtils.java b/src/java/com/android/internal/telephony/uicc/PortUtils.java
new file mode 100644
index 0000000..4a18b56
--- /dev/null
+++ b/src/java/com/android/internal/telephony/uicc/PortUtils.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 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.uicc;
+
+import android.annotation.NonNull;
+
+import com.android.internal.telephony.uicc.IccSlotStatus.MultipleEnabledProfilesMode;
+
+/**
+ * Various methods, useful for dealing with port.
+ */
+public class PortUtils {
+
+    /**
+     * Converts the port index to compatible with the HAL.
+     *
+     * @param mepMode   supported MultipleEnabledProfilesMode
+     * @param portIndex port index
+     * @return target index according to the MultipleEnabledProfilesMode
+     */
+    public static int convertToHalPortIndex(@NonNull MultipleEnabledProfilesMode mepMode,
+            int portIndex) {
+        // In case of MEP-A1 and MEP-A2, profiles are selected on eSIM Ports 1 and higher, hence
+        // HAL expects the ports are indexed with 1, 2... etc.
+        // So inorder to compatible with HAL, shift the port index.
+        return mepMode.isMepAMode() ? ++portIndex : portIndex;
+    }
+
+    /**
+     * Converts the port index to compatible with the HAL.
+     *
+     * @param slotIndex   physical slot index corresponding to the portIndex
+     * @param portIndex port index
+     * @return target port index according to the MultipleEnabledProfilesMode
+     */
+    public static int convertToHalPortIndex(int slotIndex, int portIndex) {
+        return convertToHalPortIndex(UiccController.getInstance().getSupportedMepMode(slotIndex),
+                portIndex);
+    }
+
+    /**
+     * Converts the port index to compatible with the platform.
+     *
+     * @param slotIndex physical slot index corresponding to the portIndex
+     * @param portIndex target port index
+     * @param cardState cardState
+     * @param supportedMepMode supported MEP mode
+     * @return shifted port index according to the MultipleEnabledProfilesMode
+     */
+    public static int convertFromHalPortIndex(int slotIndex, int portIndex,
+            IccCardStatus.CardState cardState, MultipleEnabledProfilesMode supportedMepMode) {
+        // In case of MEP-A1 and MEP-A2, profiles are selected on eSIM Ports 1 and higher.
+        // But inorder to platform code MEP mode agnostic, platform always expects the ports
+        // are indexed with 0, 1... etc. Hence shift the target port index to be compatible
+        // with platform.
+
+        // When the SIM_STATUS is related to CARDSTATE_ABSENT, CardStatus will not contain proper
+        // MEP mode info, fallback onto to the supportedMepMode data available in UiccSlot.
+        MultipleEnabledProfilesMode mepMode = cardState.isCardPresent() ? supportedMepMode
+                : UiccController.getInstance().getSupportedMepMode(slotIndex);
+        return mepMode.isMepAMode() ? --portIndex : portIndex;
+    }
+}
diff --git a/src/java/com/android/internal/telephony/uicc/RuimRecords.java b/src/java/com/android/internal/telephony/uicc/RuimRecords.java
old mode 100755
new mode 100644
index 041b5dd..2e490e3
--- a/src/java/com/android/internal/telephony/uicc/RuimRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/RuimRecords.java
@@ -690,7 +690,7 @@
                 mIccId = IccUtils.bcdToString(data, 0, data.length);
                 mFullIccId = IccUtils.bchToString(data, 0, data.length);
 
-                log("iccid: " + SubscriptionInfo.givePrintableIccid(mFullIccId));
+                log("iccid: " + SubscriptionInfo.getPrintableId(mFullIccId));
 
             break;
 
@@ -817,7 +817,6 @@
         mLoaded.set(true);
         mRecordsLoadedRegistrants.notifyRegistrants(new AsyncResult(null, null, null));
 
-        // TODO: The below is hacky since the SubscriptionController may not be ready at this time.
         if (!TextUtils.isEmpty(mMdn)) {
             int phoneId = mParentApp.getUiccProfile().getPhoneId();
             int subId = SubscriptionManager.getSubscriptionId(phoneId);
diff --git a/src/java/com/android/internal/telephony/uicc/SIMRecords.java b/src/java/com/android/internal/telephony/uicc/SIMRecords.java
index 485838b..a97b00b 100644
--- a/src/java/com/android/internal/telephony/uicc/SIMRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/SIMRecords.java
@@ -35,6 +35,7 @@
 import android.util.Log;
 import android.util.Pair;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.MccTable;
 import com.android.internal.telephony.SmsConstants;
@@ -167,7 +168,7 @@
     private static final int EVENT_UPDATE_DONE = 14 + SIM_RECORD_EVENT_BASE;
     protected static final int EVENT_GET_PNN_DONE = 15 + SIM_RECORD_EVENT_BASE;
     protected static final int EVENT_GET_OPL_DONE = 16 + SIM_RECORD_EVENT_BASE;
-    private static final int EVENT_GET_SST_DONE = 17 + SIM_RECORD_EVENT_BASE;
+    protected static final int EVENT_GET_SST_DONE = 17 + SIM_RECORD_EVENT_BASE;
     private static final int EVENT_GET_ALL_SMS_DONE = 18 + SIM_RECORD_EVENT_BASE;
     private static final int EVENT_MARK_SMS_READ_DONE = 19 + SIM_RECORD_EVENT_BASE;
     private static final int EVENT_SET_MBDN_DONE = 20 + SIM_RECORD_EVENT_BASE;
@@ -190,7 +191,6 @@
     private static final int EVENT_SET_FPLMN_DONE = 43 + SIM_RECORD_EVENT_BASE;
     protected static final int EVENT_GET_SMSS_RECORD_DONE = 46 + SIM_RECORD_EVENT_BASE;
     protected static final int EVENT_GET_PSISMSC_DONE = 47 + SIM_RECORD_EVENT_BASE;
-    protected static final int EVENT_GET_FDN_DONE = 48 + SIM_RECORD_EVENT_BASE;
 
     // ***** Constructor
 
@@ -280,6 +280,18 @@
         return mUsimServiceTable;
     }
 
+    /**
+     * Fetches the USIM service table from UsimServiceTable
+     *
+     * @return HexString representation of USIM service table
+     */
+    public String getSimServiceTable() {
+        if (mUsimServiceTable != null) {
+            return IccUtils.bytesToHexString(mUsimServiceTable.getUSIMServiceTable());
+        }
+        return null;
+    }
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private int getExtFromEf(int ef) {
         int ext;
@@ -387,18 +399,20 @@
         mNewVoiceMailTag = alphaTag;
 
         AdnRecord adn = new AdnRecord(mNewVoiceMailTag, mNewVoiceMailNum);
-
         if (mMailboxIndex != 0 && mMailboxIndex != 0xff) {
 
             new AdnRecordLoader(mFh).updateEF(adn, EF_MBDN, EF_EXT6,
                     mMailboxIndex, null,
-                    obtainMessage(EVENT_SET_MBDN_DONE, onComplete));
+                    obtainMessage(EVENT_SET_MBDN_DONE, AdnRecordLoader.VOICEMAIL_ALPHATAG_ARG,
+                            0 /* ignored arg2 */, onComplete));
 
         } else if (isCphsMailboxEnabled()) {
 
             new AdnRecordLoader(mFh).updateEF(adn, EF_MAILBOX_CPHS,
                     EF_EXT1, 1, null,
-                    obtainMessage(EVENT_SET_CPHS_MAILBOX_DONE, onComplete));
+                    obtainMessage(EVENT_SET_CPHS_MAILBOX_DONE,
+                            AdnRecordLoader.VOICEMAIL_ALPHATAG_ARG,
+                            0 /* ignored arg2 */, onComplete));
 
         } else {
             AsyncResult.forMessage((onComplete)).exception =
@@ -844,7 +858,7 @@
                     mIccId = IccUtils.bcdToString(data, 0, data.length);
                     mFullIccId = IccUtils.bchToString(data, 0, data.length);
 
-                    log("iccid: " + SubscriptionInfo.givePrintableIccid(mFullIccId));
+                    log("iccid: " + SubscriptionInfo.getPrintableId(mFullIccId));
                     break;
 
                 case EVENT_GET_AD_DONE:
@@ -1021,10 +1035,20 @@
 
                     if (DBG) log("EVENT_SET_MBDN_DONE ex:" + ar.exception);
                     if (ar.exception == null) {
+                        /**
+                         * Check for any changes made to voicemail alphaTag while saving to SIM.
+                         * if voicemail alphaTag length is more than allowed limit of SIM EF then
+                         * null alphaTag will be saved to SIM {@code AdnRecordLoader}.
+                         */
+                        if (ar.result != null) {
+                            AdnRecord adnRecord = (AdnRecord) (ar.result);
+                            if (adnRecord != null) {
+                                mNewVoiceMailTag = adnRecord.mAlphaTag;
+                            }
+                        }
                         mVoiceMailNum = mNewVoiceMailNum;
                         mVoiceMailTag = mNewVoiceMailTag;
                     }
-
                     if (isCphsMailboxEnabled()) {
                         adn = new AdnRecord(mVoiceMailTag, mVoiceMailNum);
                         Message onCphsCompleted = (Message) ar.userObj;
@@ -1048,7 +1072,8 @@
                         new AdnRecordLoader(mFh)
                                 .updateEF(adn, EF_MAILBOX_CPHS, EF_EXT1, 1, null,
                                 obtainMessage(EVENT_SET_CPHS_MAILBOX_DONE,
-                                        onCphsCompleted));
+                                       AdnRecordLoader.VOICEMAIL_ALPHATAG_ARG,
+                                        0 /* ignored arg2 */, onCphsCompleted));
                     } else {
                         if (ar.userObj != null) {
                             CarrierConfigManager configManager = (CarrierConfigManager)
@@ -1080,6 +1105,12 @@
                     isRecordLoadResponse = false;
                     ar = (AsyncResult) msg.obj;
                     if (ar.exception == null) {
+                        if (ar.result != null) {
+                            AdnRecord adnRecord = (AdnRecord) (ar.result);
+                            if (adnRecord != null) {
+                                mNewVoiceMailTag = adnRecord.mAlphaTag;
+                            }
+                        }
                         mVoiceMailNum = mNewVoiceMailNum;
                         mVoiceMailTag = mNewVoiceMailTag;
                     } else {
@@ -1328,15 +1359,6 @@
                     }
                     break;
 
-                case EVENT_GET_FDN_DONE:
-                    ar = (AsyncResult) msg.obj;
-                    if (ar.exception != null) {
-                        loge("Failed to read USIM EF_FDN field error=" + ar.exception);
-                    } else {
-                        log("EF_FDN read successfully");
-                    }
-                    break;
-
                 default:
                     super.handleMessage(msg);   // IccRecords handles generic record load responses
             }
@@ -2162,13 +2184,9 @@
         log("[CSP] Value Added Service Group (0xC0), not found!");
     }
 
-    public void loadFdnRecords() {
-        if (mParentApp != null && mParentApp.getIccFdnEnabled()
-                && mParentApp.getIccFdnAvailable()) {
-            log("Loading FdnRecords");
-            mAdnCache.requestLoadAllAdnLike(IccConstants.EF_FDN, getExtFromEf(IccConstants.EF_FDN),
-                    obtainMessage(EVENT_GET_FDN_DONE));
-        }
+    @VisibleForTesting
+    public void setMailboxIndex(int mailboxIndex) {
+        mMailboxIndex = mailboxIndex;
     }
 
     @Override
diff --git a/src/java/com/android/internal/telephony/uicc/UiccCard.java b/src/java/com/android/internal/telephony/uicc/UiccCard.java
index 689e4b7..960a166 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccCard.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccCard.java
@@ -21,16 +21,20 @@
 import android.os.Build;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.util.IndentingPrintWriter;
 
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.uicc.IccCardStatus.CardState;
+import com.android.internal.telephony.uicc.IccSlotStatus.MultipleEnabledProfilesMode;
 import com.android.internal.telephony.uicc.euicc.EuiccCard;
 import com.android.internal.telephony.uicc.euicc.EuiccPort;
+import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 
 /**
  * {@hide}
@@ -49,17 +53,17 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private CardState mCardState;
     protected String mCardId;
-    protected boolean mIsSupportsMultipleEnabledProfiles;
+    protected MultipleEnabledProfilesMode mSupportedMepMode;
 
-    protected HashMap<Integer, UiccPort> mUiccPorts = new HashMap<>();
+    protected LinkedHashMap<Integer, UiccPort> mUiccPorts = new LinkedHashMap<>();
     private HashMap<Integer, Integer> mPhoneIdToPortIdx = new HashMap<>();
 
     public UiccCard(Context c, CommandsInterface ci, IccCardStatus ics, int phoneId, Object lock,
-            boolean isSupportsMultipleEnabledProfiles) {
+            MultipleEnabledProfilesMode supportedMepMode) {
         if (DBG) log("Creating");
         mCardState = ics.mCardState;
         mLock = lock;
-        mIsSupportsMultipleEnabledProfiles = isSupportsMultipleEnabledProfiles;
+        mSupportedMepMode = supportedMepMode;
         update(c, ci, ics, phoneId);
     }
 
@@ -109,7 +113,7 @@
                 if (port == null) {
                     if (this instanceof EuiccCard) {
                         port = new EuiccPort(c, ci, ics, phoneId, mLock, this,
-                                mIsSupportsMultipleEnabledProfiles); // eSim
+                                mSupportedMepMode); // eSim
                     } else {
                         port = new UiccPort(c, ci, ics, phoneId, mLock, this); // pSim
                     }
@@ -143,13 +147,13 @@
 
 
     /**
-     * Updates MEP(Multiple Enabled Profile) support flag.
+     * Updates MEP(Multiple Enabled Profile) supported mode flag.
      *
      * <p>If IccSlotStatus comes later, the number of ports reported is only known after the
      * UiccCard creation which will impact UICC MEP capability.
      */
-    public void updateSupportMultipleEnabledProfile(boolean supported) {
-        mIsSupportsMultipleEnabledProfiles = supported;
+    public void updateSupportedMepMode(MultipleEnabledProfilesMode supportedMepMode) {
+        mSupportedMepMode = supportedMepMode;
     }
 
     @UnsupportedAppUsage
@@ -167,8 +171,11 @@
         if (!TextUtils.isEmpty(mCardId)) {
             return mCardId;
         } else {
-            UiccProfile uiccProfile = mUiccPorts.get(TelephonyManager.DEFAULT_PORT_INDEX)
-                    .getUiccProfile();
+            UiccPort uiccPort = mUiccPorts.get(TelephonyManager.DEFAULT_PORT_INDEX);
+            if (uiccPort == null) {
+                return null;
+            }
+            UiccProfile uiccProfile = uiccPort.getUiccProfile();
             return uiccProfile == null ? null : uiccProfile.getIccId();
         }
     }
@@ -210,15 +217,20 @@
         Rlog.e(LOG_TAG, msg);
     }
 
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+    public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
+        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
         pw.println("UiccCard:");
-        pw.println(" mCardState=" + mCardState);
-        pw.println(" mCardId=" + mCardId);
-        pw.println(" mNumberOfPorts=" + mUiccPorts.size());
-        pw.println( "mIsSupportsMultipleEnabledProfiles=" + mIsSupportsMultipleEnabledProfiles);
-        pw.println();
+        pw.increaseIndent();
+        pw.println("mCardState=" + mCardState);
+        pw.println("mCardId=" + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mCardId));
+        pw.println("mNumberOfPorts=" + mUiccPorts.size());
+        pw.println("mSupportedMepMode=" + mSupportedMepMode);
+        pw.println("mUiccPorts= size=" + mUiccPorts.size());
+        pw.increaseIndent();
         for (UiccPort uiccPort : mUiccPorts.values()) {
             uiccPort.dump(fd, pw, args);
         }
+        pw.decreaseIndent();
+        pw.decreaseIndent();
     }
 }
diff --git a/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java b/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java
index 9959d9b..fe19e99 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java
@@ -60,6 +60,9 @@
      */
     public static final int AUTH_CONTEXT_EAP_SIM = PhoneConstants.AUTH_CONTEXT_EAP_SIM;
     public static final int AUTH_CONTEXT_EAP_AKA = PhoneConstants.AUTH_CONTEXT_EAP_AKA;
+    public static final int AUTH_CONTEXT_GBA_BOOTSTRAP = PhoneConstants.AUTH_CONTEXT_GBA_BOOTSTRAP;
+    public static final int AUTHTYPE_GBA_NAF_KEY_EXTERNAL =
+            PhoneConstants.AUTHTYPE_GBA_NAF_KEY_EXTERNAL;
     public static final int AUTH_CONTEXT_UNDEFINED = PhoneConstants.AUTH_CONTEXT_UNDEFINED;
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -268,7 +271,7 @@
                 loge("Bogus facility lock response");
             }
             if (mIccFdnEnabled && mIccFdnAvailable) {
-                ((SIMRecords) mIccRecords).loadFdnRecords();
+                mIccRecords.loadFdnRecords();
             }
         }
     }
diff --git a/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java b/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
index ec4a51b..596ccf6 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
@@ -427,8 +427,8 @@
                 if (ar.exception == null && ar.result != null) {
                     mChannelId = ((int[]) ar.result)[0];
                     mUiccProfile.iccTransmitApduLogicalChannel(mChannelId, CLA, COMMAND, P1, P2, P3,
-                            DATA, obtainMessage(EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE, mChannelId,
-                                    mAIDInUse));
+                            DATA, false /*isEs10Command*/, obtainMessage(
+                                    EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE, mChannelId, mAIDInUse));
                 } else {
                     if (shouldRetry(ar, mRetryCount)) {
                         log("should retry");
@@ -484,7 +484,7 @@
                                 }
                             } else {
                                 mUiccProfile.iccTransmitApduLogicalChannel(mChannelId, CLA, COMMAND,
-                                        P1, P2_EXTENDED_DATA, P3, DATA,
+                                        P1, P2_EXTENDED_DATA, P3, DATA, false /*isEs10Command*/,
                                         obtainMessage(EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE,
                                                 mChannelId, mAIDInUse));
                                 break;
@@ -519,7 +519,7 @@
                     }
                 }
 
-                mUiccProfile.iccCloseLogicalChannel(mChannelId, obtainMessage(
+                mUiccProfile.iccCloseLogicalChannel(mChannelId, false /*isEs10*/, obtainMessage(
                         EVENT_CLOSE_LOGICAL_CHANNEL_DONE, 0, mAIDInUse));
                 mChannelId = -1;
                 break;
diff --git a/src/java/com/android/internal/telephony/uicc/UiccController.java b/src/java/com/android/internal/telephony/uicc/UiccController.java
index 6de9a65..9456467 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccController.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccController.java
@@ -34,12 +34,12 @@
 import android.os.Build;
 import android.os.Handler;
 import android.os.Message;
-import android.os.Registrant;
 import android.os.RegistrantList;
 import android.preference.PreferenceManager;
 import android.sysprop.TelephonyProperties;
 import android.telephony.AnomalyReporter;
 import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.TelephonyManager.SimState;
@@ -48,6 +48,7 @@
 import android.telephony.UiccSlotMapping;
 import android.telephony.data.ApnSetting;
 import android.text.TextUtils;
+import android.util.IndentingPrintWriter;
 import android.util.LocalLog;
 import android.util.Log;
 
@@ -62,11 +63,11 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.RadioConfig;
-import com.android.internal.telephony.SubscriptionInfoUpdater;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.uicc.euicc.EuiccCard;
+import com.android.internal.telephony.util.ArrayUtils;
 import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.telephony.Rlog;
 
@@ -76,6 +77,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.UUID;
+import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 
 /**
@@ -741,38 +743,6 @@
         }
     }
 
-    static void updateInternalIccStateForInactivePort(
-            Context context, int prevActivePhoneId, String iccId) {
-        if (SubscriptionManager.isValidPhoneId(prevActivePhoneId)) {
-            // Mark SIM state as ABSENT on previously phoneId.
-            TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(
-                    Context.TELEPHONY_SERVICE);
-            telephonyManager.setSimStateForPhone(prevActivePhoneId,
-                    IccCardConstants.State.ABSENT.toString());
-        }
-
-        SubscriptionInfoUpdater subInfoUpdator = PhoneFactory.getSubscriptionInfoUpdater();
-        if (subInfoUpdator != null) {
-            subInfoUpdator.updateInternalIccStateForInactivePort(prevActivePhoneId, iccId);
-        } else {
-            Rlog.e(LOG_TAG, "subInfoUpdate is null.");
-        }
-    }
-
-    static void updateInternalIccState(Context context, IccCardConstants.State state, String reason,
-            int phoneId) {
-        TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(
-                Context.TELEPHONY_SERVICE);
-        telephonyManager.setSimStateForPhone(phoneId, state.toString());
-
-        SubscriptionInfoUpdater subInfoUpdator = PhoneFactory.getSubscriptionInfoUpdater();
-        if (subInfoUpdator != null) {
-            subInfoUpdator.updateInternalIccState(getIccStateIntentString(state), reason, phoneId);
-        } else {
-            Rlog.e(LOG_TAG, "subInfoUpdate is null.");
-        }
-    }
-
     /**
      * Update SIM state for the inactive eSIM port.
      *
@@ -989,7 +959,7 @@
             log("updateSimState: phoneId=" + phoneId + ", state=" + state + ", reason="
                     + reason);
             if (!SubscriptionManager.isValidPhoneId(phoneId)) {
-                Rlog.e(LOG_TAG, "updateInternalIccState: Invalid phone id " + phoneId);
+                Rlog.e(LOG_TAG, "updateSimState: Invalid phone id " + phoneId);
                 return;
             }
 
@@ -1061,10 +1031,10 @@
 
         IccCardStatus status = (IccCardStatus)ar.result;
 
-        logWithLocalLog("onGetIccCardStatusDone: phoneId " + index + " IccCardStatus: " + status);
+        logWithLocalLog("onGetIccCardStatusDone: phoneId-" + index + " IccCardStatus: " + status);
 
         int slotId = status.mSlotPortMapping.mPhysicalSlotIndex;
-        if (VDBG) log("onGetIccCardStatusDone: phoneId " + index + " physicalSlotIndex " + slotId);
+        if (VDBG) log("onGetIccCardStatusDone: phoneId-" + index + " physicalSlotIndex " + slotId);
         if (slotId == INVALID_SLOT_ID) {
             slotId = index;
         }
@@ -1095,6 +1065,14 @@
             return;
         }
 
+        UiccPort port = card.getUiccPort(status.mSlotPortMapping.mPortIndex);
+        if (port == null) {
+            if (DBG) log("mUiccSlots[" + slotId + "] has no UiccPort with index["
+                    + status.mSlotPortMapping.mPortIndex + "]. Notifying IccChangedRegistrants");
+            mIccChangedRegistrants.notifyRegistrants(new AsyncResult(null, index, null));
+            return;
+        }
+
         String cardString = null;
         boolean isEuicc = mUiccSlots[slotId].isEuicc();
         if (isEuicc) {
@@ -1756,6 +1734,18 @@
         return mUseRemovableEsimAsDefault;
     }
 
+    /**
+     * Returns the MEP mode supported by the UiccSlot associated with slotIndex.
+     * @param slotIndex physical slot index
+     * @return MultipleEnabledProfilesMode supported by the slot
+     */
+    public IccSlotStatus.MultipleEnabledProfilesMode getSupportedMepMode(int slotIndex) {
+        synchronized (mLock) {
+            UiccSlot slot = getUiccSlot(slotIndex);
+            return slot != null ? slot.getSupportedMepMode()
+                    : IccSlotStatus.MultipleEnabledProfilesMode.NONE;
+        }
+    }
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private void log(String string) {
@@ -1777,35 +1767,40 @@
         sLocalLog.log(data);
     }
 
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("UiccController: " + this);
-        pw.println(" mContext=" + mContext);
-        pw.println(" mInstance=" + mInstance);
-        pw.println(" mIccChangedRegistrants: size=" + mIccChangedRegistrants.size());
-        for (int i = 0; i < mIccChangedRegistrants.size(); i++) {
-            pw.println("  mIccChangedRegistrants[" + i + "]="
-                    + ((Registrant)mIccChangedRegistrants.get(i)).getHandler());
+    private List<String> getPrintableCardStrings() {
+        if (!ArrayUtils.isEmpty(mCardStrings)) {
+            return mCardStrings.stream().map(SubscriptionInfo::getPrintableId).collect(
+                    Collectors.toList());
         }
-        pw.println();
-        pw.flush();
-        pw.println(" mIsCdmaSupported=" + isCdmaSupported(mContext));
-        pw.println(" mHasBuiltInEuicc=" + mHasBuiltInEuicc);
-        pw.println(" mHasActiveBuiltInEuicc=" + mHasActiveBuiltInEuicc);
-        pw.println(" mCardStrings=" + mCardStrings);
-        pw.println(" mDefaultEuiccCardId=" + mDefaultEuiccCardId);
-        pw.println(" mPhoneIdToSlotId=" + Arrays.toString(mPhoneIdToSlotId));
-        pw.println(" mUseRemovableEsimAsDefault=" + mUseRemovableEsimAsDefault);
-        pw.println(" mUiccSlots: size=" + mUiccSlots.length);
+        return mCardStrings;
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
+        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
+        pw.println("mIsCdmaSupported=" + isCdmaSupported(mContext));
+        pw.println("mHasBuiltInEuicc=" + mHasBuiltInEuicc);
+        pw.println("mHasActiveBuiltInEuicc=" + mHasActiveBuiltInEuicc);
+        pw.println("mCardStrings=" + getPrintableCardStrings());
+        pw.println("mDefaultEuiccCardId=" + mDefaultEuiccCardId);
+        pw.println("mPhoneIdToSlotId=" + Arrays.toString(mPhoneIdToSlotId));
+        pw.println("mUseRemovableEsimAsDefault=" + mUseRemovableEsimAsDefault);
+        pw.println("mUiccSlots: size=" + mUiccSlots.length);
+        pw.increaseIndent();
         for (int i = 0; i < mUiccSlots.length; i++) {
             if (mUiccSlots[i] == null) {
-                pw.println("  mUiccSlots[" + i + "]=null");
+                pw.println("mUiccSlots[" + i + "]=null");
             } else {
-                pw.println("  mUiccSlots[" + i + "]=" + mUiccSlots[i]);
+                pw.println("mUiccSlots[" + i + "]:");
+                pw.increaseIndent();
                 mUiccSlots[i].dump(fd, pw, args);
+                pw.decreaseIndent();
             }
         }
-        pw.println(" sLocalLog= ");
-        sLocalLog.dump(fd, pw, args);
+        pw.decreaseIndent();
+        pw.println();
+        pw.println("sLocalLog= ");
+        pw.increaseIndent();
         mPinStorage.dump(fd, pw, args);
+        sLocalLog.dump(fd, pw, args);
     }
 }
diff --git a/src/java/com/android/internal/telephony/uicc/UiccPkcs15.java b/src/java/com/android/internal/telephony/uicc/UiccPkcs15.java
index 9543908..be045c4 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccPkcs15.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccPkcs15.java
@@ -81,7 +81,7 @@
         private void selectFile() {
             if (mChannelId >= 0) {
                 mUiccProfile.iccTransmitApduLogicalChannel(mChannelId, 0x00, 0xA4, 0x00, 0x04, 0x02,
-                        mFileId, obtainMessage(EVENT_SELECT_FILE_DONE));
+                        mFileId, false /*isEs10Command*/, obtainMessage(EVENT_SELECT_FILE_DONE));
             } else {
                 log("EF based");
             }
@@ -90,7 +90,7 @@
         private void readBinary() {
             if (mChannelId >=0 ) {
                 mUiccProfile.iccTransmitApduLogicalChannel(mChannelId, 0x00, 0xB0, 0x00, 0x00, 0x00,
-                        "", obtainMessage(EVENT_READ_BINARY_DONE));
+                        "",  false /*isEs10Command*/, obtainMessage(EVENT_READ_BINARY_DONE));
             } else {
                 log("EF based");
             }
@@ -281,8 +281,8 @@
     private void cleanUp() {
         log("cleanUp");
         if (mChannelId >= 0) {
-            mUiccProfile.iccCloseLogicalChannel(mChannelId, obtainMessage(
-                    EVENT_CLOSE_LOGICAL_CHANNEL_DONE));
+            mUiccProfile.iccCloseLogicalChannel(mChannelId, false /*isEs10*/,
+                    obtainMessage(EVENT_CLOSE_LOGICAL_CHANNEL_DONE));
             mChannelId = -1;
         }
         mLoadedCallback.sendToTarget();
diff --git a/src/java/com/android/internal/telephony/uicc/UiccPort.java b/src/java/com/android/internal/telephony/uicc/UiccPort.java
index ad00066..d89eab1 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccPort.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccPort.java
@@ -217,12 +217,12 @@
     /**
      * Exposes {@link CommandsInterface#iccCloseLogicalChannel}
      * @deprecated Please use
-     * {@link UiccProfile#iccCloseLogicalChannel(int, Message)} instead.
+     * {@link UiccProfile#iccCloseLogicalChannel(int, boolean, Message)} instead.
      */
     @Deprecated
     public void iccCloseLogicalChannel(int channel, Message response) {
         if (mUiccProfile != null) {
-            mUiccProfile.iccCloseLogicalChannel(channel, response);
+            mUiccProfile.iccCloseLogicalChannel(channel, false /*isEs10*/, response);
         } else {
             loge("iccCloseLogicalChannel Failed!");
         }
@@ -231,15 +231,15 @@
     /**
      * Exposes {@link CommandsInterface#iccTransmitApduLogicalChannel}
      * @deprecated Please use {@link
-     * UiccProfile#iccTransmitApduLogicalChannel(int, int, int, int, int, int, String, Message)}
-     * instead.
+     * UiccProfile#iccTransmitApduLogicalChannel(int, int, int, int, int, int, String,
+     * boolean, Message)} instead.
      */
     @Deprecated
     public void iccTransmitApduLogicalChannel(int channel, int cla, int command,
             int p1, int p2, int p3, String data, Message response) {
         if (mUiccProfile != null) {
             mUiccProfile.iccTransmitApduLogicalChannel(channel, cla, command, p1, p2, p3,
-                    data, response);
+                    data, false /*isEs10Command*/, response);
         } else {
             loge("iccTransmitApduLogicalChannel Failed!");
         }
@@ -365,7 +365,7 @@
         pw.increaseIndent();
         pw.println("mPortIdx=" + mPortIdx);
         pw.println("mCi=" + mCi);
-        pw.println("mIccid=" + SubscriptionInfo.givePrintableIccid(mIccid));
+        pw.println("mIccid=" + SubscriptionInfo.getPrintableId(mIccid));
         pw.println("mPhoneId=" + mPhoneId);
         pw.println("mPhysicalSlotIndex=" + mPhysicalSlotIndex);
         synchronized (mOpenChannelRecords) {
diff --git a/src/java/com/android/internal/telephony/uicc/UiccProfile.java b/src/java/com/android/internal/telephony/uicc/UiccProfile.java
index 5809ff9..83db022 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccProfile.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccProfile.java
@@ -63,7 +63,6 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
-import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.TelephonyStatsLog;
 import com.android.internal.telephony.cat.CatService;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
@@ -81,6 +80,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 
@@ -544,25 +544,15 @@
         if (!TextUtils.isEmpty(iso)
                 && !iso.equals(TelephonyManager.getSimCountryIsoForPhone(mPhoneId))) {
             mTelephonyManager.setSimCountryIsoForPhone(mPhoneId, iso);
-            if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-                SubscriptionManagerService.getInstance().setCountryIso(subId, iso);
-            } else {
-                SubscriptionController.getInstance().setCountryIso(iso, subId);
-            }
+            SubscriptionManagerService.getInstance().setCountryIso(subId, iso);
         }
     }
 
     private void updateCarrierNameForSubscription(int subId, int nameSource) {
         /* update display name with carrier override */
-        SubscriptionInfo subInfo;
-
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            subInfo = SubscriptionManagerService.getInstance().getActiveSubscriptionInfo(subId,
-                    mContext.getOpPackageName(), mContext.getAttributionTag());
-        } else {
-            subInfo = SubscriptionController.getInstance().getActiveSubscriptionInfo(
-                    subId, mContext.getOpPackageName(), mContext.getAttributionTag());
-        }
+        SubscriptionInfo subInfo = SubscriptionManagerService.getInstance()
+                .getActiveSubscriptionInfo(subId, mContext.getOpPackageName(),
+                        mContext.getAttributionTag());
 
         if (subInfo == null) {
             return;
@@ -573,13 +563,8 @@
 
         if (!TextUtils.isEmpty(newCarrierName) && !newCarrierName.equals(oldSubName)) {
             log("sim name[" + mPhoneId + "] = " + newCarrierName);
-            if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-                SubscriptionManagerService.getInstance().setDisplayNameUsingSrc(
-                        newCarrierName, subId, nameSource);
-            } else {
-                SubscriptionController.getInstance().setDisplayNameUsingSrc(
-                        newCarrierName, subId, nameSource);
-            }
+            SubscriptionManagerService.getInstance().setDisplayNameUsingSrc(
+                    newCarrierName, subId, nameSource);
         }
     }
 
@@ -836,13 +821,8 @@
             }
             log("setExternalState: set mPhoneId=" + mPhoneId + " mExternalState=" + mExternalState);
 
-            if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-                UiccController.getInstance().updateSimState(mPhoneId, mExternalState,
-                        getIccStateReason(mExternalState));
-            } else {
-                UiccController.updateInternalIccState(mContext, mExternalState,
-                        getIccStateReason(mExternalState), mPhoneId);
-            }
+            UiccController.getInstance().updateSimState(mPhoneId, mExternalState,
+                    getIccStateReason(mExternalState));
         }
     }
 
@@ -1443,7 +1423,7 @@
         Set<String> uninstalledCarrierPackages = new ArraySet<>();
         List<UiccAccessRule> accessRules = rules.getAccessRules();
         for (UiccAccessRule accessRule : accessRules) {
-            String certHexString = accessRule.getCertificateHexString().toUpperCase();
+            String certHexString = accessRule.getCertificateHexString().toUpperCase(Locale.ROOT);
             String pkgName = certPackageMap.get(certHexString);
             if (!TextUtils.isEmpty(pkgName) && !isPackageBundled(mContext, pkgName)) {
                 uninstalledCarrierPackages.add(pkgName);
@@ -1473,7 +1453,7 @@
             String[] keyValue = keyValueString.split(keyValueDelim);
 
             if (keyValue.length == 2) {
-                map.put(keyValue[0].toUpperCase(), keyValue[1]);
+                map.put(keyValue[0].toUpperCase(Locale.ROOT), keyValue[1]);
             } else {
                 loge("Incorrect length of key-value pair in carrier app allow list map.  "
                         + "Length should be exactly 2");
@@ -1633,9 +1613,9 @@
     /**
      * Exposes {@link CommandsInterface#iccCloseLogicalChannel}
      */
-    public void iccCloseLogicalChannel(int channel, Message response) {
+    public void iccCloseLogicalChannel(int channel, boolean isEs10, Message response) {
         logWithLocalLog("iccCloseLogicalChannel: " + channel);
-        mCi.iccCloseLogicalChannel(channel,
+        mCi.iccCloseLogicalChannel(channel, isEs10,
                 mHandler.obtainMessage(EVENT_CLOSE_LOGICAL_CHANNEL_DONE, response));
     }
 
@@ -1643,9 +1623,9 @@
      * Exposes {@link CommandsInterface#iccTransmitApduLogicalChannel}
      */
     public void iccTransmitApduLogicalChannel(int channel, int cla, int command,
-            int p1, int p2, int p3, String data, Message response) {
-        mCi.iccTransmitApduLogicalChannel(channel, cla, command, p1, p2, p3,
-                data, mHandler.obtainMessage(EVENT_TRANSMIT_APDU_LOGICAL_CHANNEL_DONE, response));
+            int p1, int p2, int p3, String data, boolean isEs10Command, Message response) {
+        mCi.iccTransmitApduLogicalChannel(channel, cla, command, p1, p2, p3, data, isEs10Command,
+                mHandler.obtainMessage(EVENT_TRANSMIT_APDU_LOGICAL_CHANNEL_DONE, response));
     }
 
     /**
@@ -1739,42 +1719,34 @@
      */
     public boolean setOperatorBrandOverride(String brand) {
         log("setOperatorBrandOverride: " + brand);
-        log("current iccId: " + SubscriptionInfo.givePrintableIccid(getIccId()));
+        log("current iccId: " + SubscriptionInfo.getPrintableId(getIccId()));
 
         String iccId = getIccId();
         if (TextUtils.isEmpty(iccId)) {
             return false;
         }
 
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            int subId = SubscriptionManager.getSubscriptionId(getPhoneId());
-            SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
-                    .getSubscriptionInfoInternal(subId);
-            if (subInfo == null) {
-                loge("setOperatorBrandOverride: Cannot find subscription info for sub " + subId);
-                return false;
-            }
+        int subId = SubscriptionManager.getSubscriptionId(getPhoneId());
+        SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
+                .getSubscriptionInfoInternal(subId);
+        if (subInfo == null) {
+            loge("setOperatorBrandOverride: Cannot find subscription info for sub " + subId);
+            return false;
+        }
 
-            List<SubscriptionInfo> subInfos = new ArrayList<>();
-            subInfos.add(subInfo.toSubscriptionInfo());
-            String groupUuid = subInfo.getGroupUuid();
-            if (!TextUtils.isEmpty(groupUuid)) {
-                subInfos.addAll(SubscriptionManagerService.getInstance()
-                        .getSubscriptionsInGroup(ParcelUuid.fromString(groupUuid),
-                                mContext.getOpPackageName(), mContext.getFeatureId()));
-            }
+        List<SubscriptionInfo> subInfos = new ArrayList<>();
+        subInfos.add(subInfo.toSubscriptionInfo());
+        String groupUuid = subInfo.getGroupUuid();
+        if (!TextUtils.isEmpty(groupUuid)) {
+            subInfos.addAll(SubscriptionManagerService.getInstance()
+                    .getSubscriptionsInGroup(ParcelUuid.fromString(groupUuid),
+                            mContext.getOpPackageName(), mContext.getFeatureId()));
+        }
 
-            if (subInfos.stream().noneMatch(info -> TextUtils.equals(IccUtils.stripTrailingFs(
-                    info.getIccId()), IccUtils.stripTrailingFs(iccId)))) {
-                loge("iccId doesn't match current active subId.");
-                return false;
-            }
-        } else {
-            if (!SubscriptionController.getInstance().checkPhoneIdAndIccIdMatch(
-                    getPhoneId(), iccId)) {
-                loge("iccId doesn't match current active subId.");
-                return false;
-            }
+        if (subInfos.stream().noneMatch(info -> TextUtils.equals(IccUtils.stripTrailingFs(
+                info.getIccId()), IccUtils.stripTrailingFs(iccId)))) {
+            loge("iccId doesn't match current active subId.");
+            return false;
         }
 
         SharedPreferences.Editor spEditor =
diff --git a/src/java/com/android/internal/telephony/uicc/UiccSlot.java b/src/java/com/android/internal/telephony/uicc/UiccSlot.java
index 2490efa..db10271 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccSlot.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccSlot.java
@@ -33,6 +33,8 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
 import android.view.WindowManager;
 
 import com.android.internal.R;
@@ -41,7 +43,9 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.uicc.IccCardStatus.CardState;
+import com.android.internal.telephony.uicc.IccSlotStatus.MultipleEnabledProfilesMode;
 import com.android.internal.telephony.uicc.euicc.EuiccCard;
+import com.android.internal.telephony.util.ArrayUtils;
 import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.telephony.Rlog;
 
@@ -79,7 +83,6 @@
     private final Object mLock = new Object();
     private boolean mActive;
     private boolean mStateIsUnknown = true;
-    private CardState mCardState;
     private Context mContext;
     private UiccCard mUiccCard;
     private boolean mIsEuicc;
@@ -87,12 +90,17 @@
     private String mEid;
     private AnswerToReset mAtr;
     private boolean mIsRemovable;
+    private MultipleEnabledProfilesMode mSupportedMepMode;
+
     // Map each available portIdx to phoneId
     private HashMap<Integer, Integer> mPortIdxToPhoneId = new HashMap<>();
     //Map each available portIdx with old radio state for state checking
     private HashMap<Integer, Integer> mLastRadioState = new HashMap<>();
     // Store iccId of each port.
     private HashMap<Integer, String> mIccIds = new HashMap<>();
+    // IccCardStatus and IccSlotStatus events order is not guaranteed. Inorder to handle MEP mode,
+    // map each available portIdx with CardState for card state checking
+    private HashMap<Integer, CardState> mCardState = new HashMap<>();
 
     private static final int EVENT_CARD_REMOVED = 13;
     private static final int EVENT_CARD_ADDED = 14;
@@ -101,28 +109,31 @@
         if (DBG) log("Creating");
         mContext = c;
         mActive = isActive;
-        mCardState = null;
+        mSupportedMepMode = MultipleEnabledProfilesMode.NONE;
     }
 
     /**
      * Update slot. The main trigger for this is a change in the ICC Card status.
      */
     public void update(CommandsInterface ci, IccCardStatus ics, int phoneId, int slotIndex) {
-        if (DBG) log("cardStatus update: " + ics.toString());
         synchronized (mLock) {
             mPortIdxToPhoneId.put(ics.mSlotPortMapping.mPortIndex, phoneId);
-            CardState oldState = mCardState;
-            mCardState = ics.mCardState;
+            CardState oldState = mCardState.get(ics.mSlotPortMapping.mPortIndex);
+            mCardState.put(ics.mSlotPortMapping.mPortIndex, ics.mCardState);
             mIccIds.put(ics.mSlotPortMapping.mPortIndex, ics.iccid);
             parseAtr(ics.atr);
             mIsRemovable = isSlotRemovable(slotIndex);
+            // Update supported MEP mode in IccCardStatus if the CardState is present.
+            if (ics.mCardState.isCardPresent()) {
+                updateSupportedMepMode(ics.mSupportedMepMode);
+            }
 
             int radioState = ci.getRadioState();
             if (DBG) {
                 log("update: radioState=" + radioState + " mLastRadioState=" + mLastRadioState);
             }
 
-            if (absentStateUpdateNeeded(oldState)) {
+            if (absentStateUpdateNeeded(oldState, ics.mSlotPortMapping.mPortIndex)) {
                 updateCardStateAbsent(ci.getRadioState(), phoneId,
                         ics.mSlotPortMapping.mPortIndex);
             // Because mUiccCard may be updated in both IccCardStatus and IccSlotStatus, we need to
@@ -130,7 +141,8 @@
             //   1. mCardState is changing from ABSENT to non ABSENT.
             //   2. The latest mCardState is not ABSENT, but there is no UiccCard instance.
             } else if ((oldState == null || oldState == CardState.CARDSTATE_ABSENT
-                    || mUiccCard == null) && mCardState != CardState.CARDSTATE_ABSENT) {
+                    || mUiccCard == null) && mCardState.get(ics.mSlotPortMapping.mPortIndex)
+                    != CardState.CARDSTATE_ABSENT) {
                 // No notification while we are just powering up
                 if (radioState != TelephonyManager.RADIO_POWER_UNAVAILABLE
                         && mLastRadioState.getOrDefault(ics.mSlotPortMapping.mPortIndex,
@@ -141,14 +153,17 @@
                 }
 
                 // card is present in the slot now; create new mUiccCard
-                if (mUiccCard != null) {
+                if (mUiccCard != null && (!mIsEuicc
+                        || ArrayUtils.isEmpty(mUiccCard.getUiccPortList()))) {
                     loge("update: mUiccCard != null when card was present; disposing it now");
                     mUiccCard.dispose();
+                    mUiccCard = null;
                 }
 
                 if (!mIsEuicc) {
                     // Uicc does not support MEP, passing false by default.
-                    mUiccCard = new UiccCard(mContext, ci, ics, phoneId, mLock, false);
+                    mUiccCard = new UiccCard(mContext, ci, ics, phoneId, mLock,
+                            MultipleEnabledProfilesMode.NONE);
                 } else {
                     // The EID should be reported with the card status, but in case it's not we want
                     // to catch that here
@@ -156,8 +171,14 @@
                         loge("update: eid is missing. ics.eid="
                                 + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, ics.eid));
                     }
-                    mUiccCard = new EuiccCard(mContext, ci, ics, phoneId, mLock,
-                            isMultipleEnabledProfileSupported());
+                    if (mUiccCard == null) {
+                        mUiccCard = new EuiccCard(mContext, ci, ics, phoneId, mLock,
+                                getSupportedMepMode());
+                    } else {
+                        // In MEP case, UiccCard instance is already created, just call update API.
+                        // UiccPort initialization is handled inside UiccCard.
+                        mUiccCard.update(mContext, ci, ics, phoneId);
+                    }
                 }
             } else {
                 if (mUiccCard != null) {
@@ -172,37 +193,30 @@
      * Update slot based on IccSlotStatus.
      */
     public void update(CommandsInterface[] ci, IccSlotStatus iss, int slotIndex) {
-        if (DBG) log("slotStatus update: " + iss.toString());
         synchronized (mLock) {
             IccSimPortInfo[] simPortInfos = iss.mSimPortInfos;
-            CardState oldState = mCardState;
             parseAtr(iss.atr);
-            mCardState = iss.cardState;
             mEid = iss.eid;
             mIsRemovable = isSlotRemovable(slotIndex);
 
             for (int i = 0; i < simPortInfos.length; i++) {
                 int phoneId = iss.mSimPortInfos[i].mLogicalSlotIndex;
+                CardState oldState = mCardState.get(i);
+                mCardState.put(i, iss.cardState);
                 mIccIds.put(i, simPortInfos[i].mIccId);
                 if (!iss.mSimPortInfos[i].mPortActive) {
                     // TODO: (b/79432584) evaluate whether should broadcast card state change
                     // even if it's inactive.
-                    if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-                        UiccController.getInstance().updateSimStateForInactivePort(
-                                mPortIdxToPhoneId.getOrDefault(i, INVALID_PHONE_ID),
-                                iss.mSimPortInfos[i].mIccId);
-                    } else {
-                        UiccController.updateInternalIccStateForInactivePort(mContext,
-                                mPortIdxToPhoneId.getOrDefault(i, INVALID_PHONE_ID),
-                                iss.mSimPortInfos[i].mIccId);
-                    }
+                    UiccController.getInstance().updateSimStateForInactivePort(
+                            mPortIdxToPhoneId.getOrDefault(i, INVALID_PHONE_ID),
+                            iss.mSimPortInfos[i].mIccId);
                     mLastRadioState.put(i, TelephonyManager.RADIO_POWER_UNAVAILABLE);
                     if (mUiccCard != null) {
                         // Dispose the port
                         mUiccCard.disposePort(i);
                     }
                 } else {
-                    if (absentStateUpdateNeeded(oldState)) {
+                    if (absentStateUpdateNeeded(oldState, i)) {
                         int radioState = SubscriptionManager.isValidPhoneId(phoneId) ?
                                 ci[phoneId].getRadioState() :
                                 TelephonyManager.RADIO_POWER_UNAVAILABLE;
@@ -229,10 +243,28 @@
                 mPortIdxToPhoneId.put(i, simPortInfos[i].mPortActive ?
                         simPortInfos[i].mLogicalSlotIndex : INVALID_PHONE_ID);
             }
-            // Since the MEP capability is related with number ports reported, thus need to
+            updateSupportedMepMode(iss.mSupportedMepMode);
+            // Since the MEP capability is related to supported MEP mode, thus need to
             // update the flag after UiccCard creation.
             if (mUiccCard != null) {
-                mUiccCard.updateSupportMultipleEnabledProfile(isMultipleEnabledProfileSupported());
+                mUiccCard.updateSupportedMepMode(getSupportedMepMode());
+            }
+        }
+    }
+
+    private void updateSupportedMepMode(MultipleEnabledProfilesMode mode) {
+        mSupportedMepMode = mode;
+        // If SupportedMepMode is MultipleEnabledProfilesMode.NONE, validate ATR and
+        // num of ports to handle backward compatibility for < RADIO_HAL_VERSION_2_1.
+        if (mode == MultipleEnabledProfilesMode.NONE) {
+            // Even ATR suggest UICC supports multiple enabled profiles, MEP can be disabled per
+            // carrier restrictions, so checking the real number of ports reported from modem is
+            // necessary.
+            if (mPortIdxToPhoneId.size() > 1
+                    && mAtr != null && mAtr.isMultipleEnabledProfilesSupported()) {
+                // Set MEP-B mode in case if modem sends wrong mode even though supports MEP.
+                Log.i(TAG, "Modem does not send proper supported MEP mode or older HAL version");
+                mSupportedMepMode = MultipleEnabledProfilesMode.MEP_B;
             }
         }
     }
@@ -306,16 +338,14 @@
 
     /* Returns true if multiple enabled profiles are supported */
     public boolean isMultipleEnabledProfileSupported() {
-        // even ATR suggest UICC supports multiple enabled profiles, MEP can be disabled per
-        // carrier restrictions, so checking the real number of ports reported from modem is
-        // necessary.
-        return mPortIdxToPhoneId.size() > 1 && mAtr != null &&
-                mAtr.isMultipleEnabledProfilesSupported();
+        synchronized (mLock) {
+            return mSupportedMepMode.isMepMode();
+        }
     }
 
-    private boolean absentStateUpdateNeeded(CardState oldState) {
+    private boolean absentStateUpdateNeeded(CardState oldState, int portIndex) {
         return (oldState != CardState.CARDSTATE_ABSENT || mUiccCard != null)
-                && mCardState == CardState.CARDSTATE_ABSENT;
+                && mCardState.get(portIndex) == CardState.CARDSTATE_ABSENT;
     }
 
     private void updateCardStateAbsent(int radioState, int phoneId, int portIndex) {
@@ -328,15 +358,12 @@
             sendMessage(obtainMessage(EVENT_CARD_REMOVED, null));
         }
 
-        if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-            UiccController.getInstance().updateSimState(phoneId, IccCardConstants.State.ABSENT,
-                    null);
-        } else {
-            UiccController.updateInternalIccState(mContext, IccCardConstants.State.ABSENT,
-                    null, phoneId);
-        }
-        // no card present in the slot now; dispose card and make mUiccCard null
-        nullifyUiccCard(false /* sim state is not unknown */);
+        UiccController.getInstance().updateSimState(phoneId, IccCardConstants.State.ABSENT, null);
+        // no card present in the slot now; dispose port and then card if needed.
+        disposeUiccCardIfNeeded(false /* sim state is not unknown */, portIndex);
+        // If SLOT_STATUS is the last event, wrong subscription is getting invalidate during
+        // slot switch event. To avoid it, reset the phoneId corresponding to the portIndex.
+        mPortIdxToPhoneId.put(portIndex, INVALID_PHONE_ID);
         mLastRadioState.put(portIndex, TelephonyManager.RADIO_POWER_UNAVAILABLE);
     }
 
@@ -351,8 +378,23 @@
         mUiccCard = null;
     }
 
+    private void disposeUiccCardIfNeeded(boolean isStateUnknown, int portIndex) {
+        if (mUiccCard != null) {
+            // First dispose UiccPort corresponding to the portIndex
+            mUiccCard.disposePort(portIndex);
+            if (ArrayUtils.isEmpty(mUiccCard.getUiccPortList())) {
+                // No UiccPort objects are found, safe to dispose the card
+                nullifyUiccCard(isStateUnknown);
+            }
+        } else {
+            mStateIsUnknown = isStateUnknown;
+        }
+    }
+
     public boolean isStateUnknown() {
-        if (mCardState == null || mCardState == CardState.CARDSTATE_ABSENT) {
+        // CardState is not specific to any port index, use default port.
+        CardState cardState = mCardState.get(TelephonyManager.DEFAULT_PORT_INDEX);
+        if (cardState == null || cardState == CardState.CARDSTATE_ABSENT) {
             // mStateIsUnknown is valid only in this scenario.
             return mStateIsUnknown;
         }
@@ -563,11 +605,9 @@
      */
     public CardState getCardState() {
         synchronized (mLock) {
-            if (mCardState == null) {
-                return CardState.CARDSTATE_ABSENT;
-            } else {
-                return mCardState;
-            }
+            // CardState is not specific to any port index, use default port.
+            CardState cardState = mCardState.get(TelephonyManager.DEFAULT_PORT_INDEX);
+            return cardState == null ? CardState.CARDSTATE_ABSENT : cardState;
         }
     }
 
@@ -581,24 +621,27 @@
     }
 
     /**
+     * Returns the supported MEP mode.
+     */
+    public MultipleEnabledProfilesMode getSupportedMepMode() {
+        synchronized (mLock) {
+            return mSupportedMepMode;
+        }
+    }
+    /**
      * Processes radio state unavailable event
      */
     public void onRadioStateUnavailable(int phoneId) {
-        nullifyUiccCard(true /* sim state is unknown */);
+        int portIndex = getPortIndexFromPhoneId(phoneId);
+        disposeUiccCardIfNeeded(true /* sim state is unknown */, portIndex);
 
         if (phoneId != INVALID_PHONE_ID) {
-            if (PhoneFactory.isSubscriptionManagerServiceEnabled()) {
-                UiccController.getInstance().updateSimState(phoneId,
-                        IccCardConstants.State.UNKNOWN, null);
-            } else {
-                UiccController.updateInternalIccState(
-                        mContext, IccCardConstants.State.UNKNOWN, null, phoneId);
-            }
-            mLastRadioState.put(getPortIndexFromPhoneId(phoneId),
-                    TelephonyManager.RADIO_POWER_UNAVAILABLE);
+            UiccController.getInstance().updateSimState(phoneId,
+                    IccCardConstants.State.UNKNOWN, null);
         }
-
-        mCardState = null;
+        mLastRadioState.put(portIndex, TelephonyManager.RADIO_POWER_UNAVAILABLE);
+        // Reset CardState
+        mCardState.put(portIndex, null);
     }
 
     private void log(String msg) {
@@ -612,32 +655,41 @@
     private Map<Integer, String> getPrintableIccIds() {
         Map<Integer, String> printableIccIds = mIccIds.entrySet().stream()
                 .collect(Collectors.toMap(Map.Entry::getKey,
-                        e -> SubscriptionInfo.givePrintableIccid(e.getValue())));
+                        e -> SubscriptionInfo.getPrintableId(e.getValue())));
         return printableIccIds;
     }
 
     /**
      * Dump
      */
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("UiccSlot:");
-        pw.println(" mActive=" + mActive);
-        pw.println(" mIsEuicc=" + mIsEuicc);
-        pw.println(" isEuiccSupportsMultipleEnabledProfiles="
-                + isMultipleEnabledProfileSupported());
-        pw.println(" mIsRemovable=" + mIsRemovable);
-        pw.println(" mLastRadioState=" + mLastRadioState);
-        pw.println(" mIccIds=" + getPrintableIccIds());
-        pw.println(" mPortIdxToPhoneId=" + mPortIdxToPhoneId);
-        pw.println(" mEid=" + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mEid));
-        pw.println(" mCardState=" + mCardState);
+    public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
+        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
+        pw.println("mActive=" + mActive);
+        pw.println("mIsEuicc=" + mIsEuicc);
+        pw.println("isEuiccSupportsMultipleEnabledProfiles=" + isMultipleEnabledProfileSupported());
+        pw.println("mIsRemovable=" + mIsRemovable);
+        pw.println("mLastRadioState=" + mLastRadioState);
+        pw.println("mIccIds=" + getPrintableIccIds());
+        pw.println("mPortIdxToPhoneId=" + mPortIdxToPhoneId);
+        pw.println("mEid=" + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mEid));
+        pw.println("mCardState=" + mCardState);
+        pw.println("mSupportedMepMode=" + mSupportedMepMode);
         if (mUiccCard != null) {
-            pw.println(" mUiccCard=" + mUiccCard);
+            pw.println("mUiccCard=");
             mUiccCard.dump(fd, pw, args);
         } else {
-            pw.println(" mUiccCard=null");
+            pw.println("mUiccCard=null");
         }
         pw.println();
         pw.flush();
     }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "[UiccSlot: mActive=" + mActive + ", mIccId=" + getPrintableIccIds() + ", mIsEuicc="
+                + mIsEuicc + ", MEP=" + isMultipleEnabledProfileSupported() + ", mPortIdxToPhoneId="
+                + mPortIdxToPhoneId + ", mEid=" + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mEid)
+                + ", mCardState=" + mCardState + " mSupportedMepMode=" + mSupportedMepMode + "]";
+    }
 }
diff --git a/src/java/com/android/internal/telephony/uicc/UsimFileHandler.java b/src/java/com/android/internal/telephony/uicc/UsimFileHandler.java
old mode 100755
new mode 100644
diff --git a/src/java/com/android/internal/telephony/uicc/UsimServiceTable.java b/src/java/com/android/internal/telephony/uicc/UsimServiceTable.java
index fc58d3c..ea2bf42 100644
--- a/src/java/com/android/internal/telephony/uicc/UsimServiceTable.java
+++ b/src/java/com/android/internal/telephony/uicc/UsimServiceTable.java
@@ -157,4 +157,8 @@
     protected Object[] getValues() {
         return UsimService.values();
     }
+
+    public byte[] getUSIMServiceTable() {
+        return mServiceTable;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/uicc/euicc/EuiccCard.java b/src/java/com/android/internal/telephony/uicc/euicc/EuiccCard.java
index fc74d29..698fbc8 100644
--- a/src/java/com/android/internal/telephony/uicc/euicc/EuiccCard.java
+++ b/src/java/com/android/internal/telephony/uicc/euicc/EuiccCard.java
@@ -29,6 +29,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.uicc.IccCardStatus;
+import com.android.internal.telephony.uicc.IccSlotStatus.MultipleEnabledProfilesMode;
 import com.android.internal.telephony.uicc.UiccCard;
 import com.android.internal.telephony.uicc.UiccPort;
 import com.android.internal.telephony.uicc.euicc.async.AsyncResultCallback;
@@ -45,8 +46,8 @@
     private RegistrantList mEidReadyRegistrants;
 
     public EuiccCard(Context c, CommandsInterface ci, IccCardStatus ics, int phoneId, Object lock,
-            boolean isSupportsMultipleEnabledProfiles) {
-        super(c, ci, ics, phoneId, lock, isSupportsMultipleEnabledProfiles);
+            MultipleEnabledProfilesMode supportedMepMode) {
+        super(c, ci, ics, phoneId, lock, supportedMepMode);
         if (TextUtils.isEmpty(ics.eid)) {
             loge("no eid given in constructor for phone " + phoneId);
             loadEidAndNotifyRegistrants();
@@ -57,17 +58,17 @@
     }
 
     /**
-     * Updates MEP(Multiple Enabled Profile) support flag.
+     * Updates MEP(Multiple Enabled Profile) supported mode flag.
      *
      * <p>If IccSlotStatus comes later, the number of ports reported is only known after the
-     * UiccCard creation which will impact UICC MEP capability.
+     * UiccCard creation which will impact UICC MEP capability in case of old HAL version.
      */
     @Override
-    public void updateSupportMultipleEnabledProfile(boolean supported) {
-        mIsSupportsMultipleEnabledProfiles = supported;
+    public void updateSupportedMepMode(MultipleEnabledProfilesMode supportedMepMode) {
+        mSupportedMepMode = supportedMepMode;
         for (UiccPort port : mUiccPorts.values()) {
             if (port instanceof EuiccPort) {
-                ((EuiccPort) port).updateSupportMultipleEnabledProfile(supported);
+                ((EuiccPort) port).updateSupportedMepMode(supportedMepMode);
             } else {
                 loge("eUICC card has non-euicc port object:" + port.toString());
             }
diff --git a/src/java/com/android/internal/telephony/uicc/euicc/EuiccPort.java b/src/java/com/android/internal/telephony/uicc/euicc/EuiccPort.java
index 639915a..3bd66f8 100644
--- a/src/java/com/android/internal/telephony/uicc/euicc/EuiccPort.java
+++ b/src/java/com/android/internal/telephony/uicc/euicc/EuiccPort.java
@@ -28,6 +28,7 @@
 import android.telephony.euicc.EuiccNotification;
 import android.telephony.euicc.EuiccRulesAuthTable;
 import android.text.TextUtils;
+import android.util.IndentingPrintWriter;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.CommandsInterface;
@@ -35,7 +36,9 @@
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.uicc.IccCardStatus;
 import com.android.internal.telephony.uicc.IccIoResult;
+import com.android.internal.telephony.uicc.IccSlotStatus.MultipleEnabledProfilesMode;
 import com.android.internal.telephony.uicc.IccUtils;
+import com.android.internal.telephony.uicc.PortUtils;
 import com.android.internal.telephony.uicc.UiccCard;
 import com.android.internal.telephony.uicc.UiccPort;
 import com.android.internal.telephony.uicc.asn1.Asn1Decoder;
@@ -101,9 +104,6 @@
     private static final String DEV_CAP_NR5GC = "nr5gc";
     private static final String DEV_CAP_EUTRAN5GC = "eutran5gc";
 
-    private static final String ATR_ESIM_OS_V_M5 =
-            "3B9F97C00AB1FE453FC6838031E073FE211F65D002341569810F21";
-
     // These interfaces are used for simplifying the code by leveraging lambdas.
     private interface ApduRequestBuilder {
         void build(RequestBuilder requestBuilder)
@@ -127,11 +127,10 @@
     private EuiccSpecVersion mSpecVersion;
     private volatile String mEid;
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    public boolean mIsSupportsMultipleEnabledProfiles;
-    private String mAtr;
+    public MultipleEnabledProfilesMode mSupportedMepMode;
 
     public EuiccPort(Context c, CommandsInterface ci, IccCardStatus ics, int phoneId, Object lock,
-            UiccCard card, boolean isSupportsMultipleEnabledProfiles) {
+            UiccCard card, MultipleEnabledProfilesMode supportedMepMode) {
         super(c, ci, ics, phoneId, lock, card);
         // TODO: Set supportExtendedApdu based on ATR.
         mApduSender = new ApduSender(ci, ISD_R_AID, false /* supportExtendedApdu */);
@@ -141,8 +140,7 @@
             mEid = ics.eid;
             mCardId = ics.eid;
         }
-        mAtr = ics.atr;
-        mIsSupportsMultipleEnabledProfiles = isSupportsMultipleEnabledProfiles;
+        mSupportedMepMode = supportedMepMode;
     }
 
     /**
@@ -165,18 +163,17 @@
             if (!TextUtils.isEmpty(ics.eid)) {
                 mEid = ics.eid;
             }
-            mAtr = ics.atr;
             super.update(c, ci, ics, uiccCard);
         }
     }
 
     /**
-     * Updates MEP(Multiple Enabled Profile) support flag.
+     * Updates MEP(Multiple Enabled Profile) supported mode flag.
      * The flag can be updated after the port creation.
      */
-    public void updateSupportMultipleEnabledProfile(boolean supported) {
-        logd("updateSupportMultipleEnabledProfile");
-        mIsSupportsMultipleEnabledProfiles = supported;
+    public void updateSupportedMepMode(MultipleEnabledProfilesMode supportedMepMode) {
+        logd("updateSupportedMepMode");
+        mSupportedMepMode = supportedMepMode;
     }
 
     /**
@@ -187,13 +184,8 @@
      * @since 1.1.0 [GSMA SGP.22]
      */
     public void getAllProfiles(AsyncResultCallback<EuiccProfileInfo[]> callback, Handler handler) {
-        byte[] profileTags;
-        if (mIsSupportsMultipleEnabledProfiles) {
-            profileTags = ATR_ESIM_OS_V_M5.equals(mAtr)
-                    ? Tags.EUICC_PROFILE_MEP_TAGS : Tags.EUICC_PROFILE_MEP_TAGS_WITH_9F20;
-        } else {
-            profileTags = Tags.EUICC_PROFILE_TAGS;
-        }
+        byte[] profileTags = mSupportedMepMode.isMepMode() ? Tags.EUICC_PROFILE_MEP_TAGS
+                : Tags.EUICC_PROFILE_TAGS;
         sendApdu(
                 newRequestProvider((RequestBuilder requestBuilder) ->
                         requestBuilder.addStoreData(Asn1Node.newBuilder(Tags.TAG_GET_PROFILES)
@@ -234,13 +226,8 @@
      */
     public final void getProfile(String iccid, AsyncResultCallback<EuiccProfileInfo> callback,
             Handler handler) {
-        byte[] profileTags;
-        if (mIsSupportsMultipleEnabledProfiles) {
-            profileTags = ATR_ESIM_OS_V_M5.equals(mAtr)
-                    ? Tags.EUICC_PROFILE_MEP_TAGS : Tags.EUICC_PROFILE_MEP_TAGS_WITH_9F20;
-        } else {
-            profileTags = Tags.EUICC_PROFILE_TAGS;
-        }
+        byte[] profileTags = mSupportedMepMode.isMepMode() ? Tags.EUICC_PROFILE_MEP_TAGS
+                : Tags.EUICC_PROFILE_TAGS;
         sendApdu(
                 newRequestProvider((RequestBuilder requestBuilder) ->
                         requestBuilder.addStoreData(Asn1Node.newBuilder(Tags.TAG_GET_PROFILES)
@@ -295,7 +282,7 @@
                             return null;
                         case CODE_PROFILE_NOT_IN_EXPECTED_STATE:
                             logd("Profile is already disabled, iccid: "
-                                    + SubscriptionInfo.givePrintableIccid(iccid));
+                                    + SubscriptionInfo.getPrintableId(iccid));
                             return null;
                         default:
                             throw new EuiccCardErrorException(
@@ -319,11 +306,20 @@
         sendApduWithSimResetErrorWorkaround(
                 newRequestProvider((RequestBuilder requestBuilder) -> {
                     byte[] iccidBytes = IccUtils.bcdToBytes(padTrailingFs(iccid));
-                    requestBuilder.addStoreData(Asn1Node.newBuilder(Tags.TAG_ENABLE_PROFILE)
+                    Asn1Node.Builder builder = Asn1Node.newBuilder(Tags.TAG_ENABLE_PROFILE)
                             .addChild(Asn1Node.newBuilder(Tags.TAG_CTX_COMP_0)
                                     .addChildAsBytes(Tags.TAG_ICCID, iccidBytes))
-                            .addChildAsBoolean(Tags.TAG_CTX_1, refresh)
-                            .build().toHex());
+                            .addChildAsBoolean(Tags.TAG_CTX_1, refresh);
+                    // Port index should be added only in case of MEP-A1 mode.
+                    if (mSupportedMepMode.isMepA1Mode()) {
+                        // In case of MEP-A1 and MEP-A2, profiles are selected on eSIM Ports 1 and
+                        // higher (refer as target port). Hence, convert the portIndex to
+                        // target port index before adding.
+                        builder.addChildAsInteger(Tags.TAG_CTX_2,
+                                PortUtils.convertToHalPortIndex(mSupportedMepMode,
+                                        super.getPortIdx()));
+                    }
+                    requestBuilder.addStoreData(builder.build().toHex());
                 }),
                 response -> {
                     int result;
@@ -334,7 +330,7 @@
                             return null;
                         case CODE_PROFILE_NOT_IN_EXPECTED_STATE:
                             logd("Profile is already enabled, iccid: "
-                                    + SubscriptionInfo.givePrintableIccid(iccid));
+                                    + SubscriptionInfo.getPrintableId(iccid));
                             return null;
                         default:
                             throw new EuiccCardErrorException(
@@ -1262,10 +1258,8 @@
             // if the Profile is in the Enabled state on the same eSIM Port as where this
             // getProfilesInfo command was sent. So should check for enabledOnEsimPort(TAG_PORT)
             // tag and verify its value is a valid port (means port value is >=0) or not.
-            if ((profileNode.hasChild(Tags.TAG_PORT)
-                    && profileNode.getChild(Tags.TAG_PORT).asInteger() >= 0)
-                    || (profileNode.hasChild(Tags.TAG_PORT_9F20)
-                    && profileNode.getChild(Tags.TAG_PORT_9F20).asInteger() >= 0)) {
+            if (profileNode.hasChild(Tags.TAG_PORT)
+                    && profileNode.getChild(Tags.TAG_PORT).asInteger() >= 0) {
                 profileBuilder.setState(EuiccProfileInfo.PROFILE_STATE_ENABLED);
             } else {
                 // noinspection WrongConstant
@@ -1430,10 +1424,13 @@
     }
 
     @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        super.dump(fd, pw, args);
+    public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
+        super.dump(fd, printWriter, args);
+        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
         pw.println("EuiccPort:");
-        pw.println(" mEid=" + mEid);
-        pw.println(" mIsSupportsMultipleEnabledProfiles=" + mIsSupportsMultipleEnabledProfiles);
+        pw.increaseIndent();
+        pw.println("mEid=" + mEid);
+        pw.println("mSupportedMepMode=" + mSupportedMepMode);
+        pw.decreaseIndent();
     }
 }
diff --git a/src/java/com/android/internal/telephony/uicc/euicc/Tags.java b/src/java/com/android/internal/telephony/uicc/euicc/Tags.java
index 3aa9ef9..befc7f9 100644
--- a/src/java/com/android/internal/telephony/uicc/euicc/Tags.java
+++ b/src/java/com/android/internal/telephony/uicc/euicc/Tags.java
@@ -84,10 +84,7 @@
     static final int TAG_PROFILE_NAME = 0x92;
     static final int TAG_OPERATOR_ID = 0xB7;
     static final int TAG_CARRIER_PRIVILEGE_RULES = 0xBF76;
-    // TODO: PORT TAG(9F20 OR 9F24) will be used based on ATR Strings because not all MEP capable
-    //  devices have M5 OS. Once modem team is ready, revert back to 9F24 TAG only.
     static final int TAG_PORT = 0x9F24;
-    static final int TAG_PORT_9F20 = 0x9F20;
 
     // Tags from the RefArDo data standard - https://source.android.com/devices/tech/config/uicc
     static final int TAG_REF_AR_DO = 0xE2;
@@ -129,22 +126,5 @@
             (byte) (TAG_PORT % 256),
     };
 
-    // TAG list for Euicc Profile with 9F20 tag.
-    // TODO: This is temporary change, should be removed once all devices are upgraded to M5 OS.
-    static final byte[] EUICC_PROFILE_MEP_TAGS_WITH_9F20 = new byte[] {
-            TAG_ICCID,
-            (byte) TAG_NICKNAME,
-            (byte) TAG_SERVICE_PROVIDER_NAME,
-            (byte) TAG_PROFILE_NAME,
-            (byte) TAG_OPERATOR_ID,
-            (byte) (TAG_PROFILE_STATE / 256),
-            (byte) (TAG_PROFILE_STATE % 256),
-            (byte) TAG_PROFILE_CLASS,
-            (byte) TAG_PROFILE_POLICY_RULE,
-            (byte) (TAG_CARRIER_PRIVILEGE_RULES / 256),
-            (byte) (TAG_CARRIER_PRIVILEGE_RULES % 256),
-            (byte) (TAG_PORT_9F20 / 256),
-            (byte) (TAG_PORT_9F20 % 256),
-    };
     private Tags() {}
 }
diff --git a/src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduCommand.java b/src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduCommand.java
index 7a8978f..8fbeb44 100644
--- a/src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduCommand.java
+++ b/src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduCommand.java
@@ -43,6 +43,12 @@
     /** Command data of an APDU as defined in GlobalPlatform Card Specification v.2.3. */
     public final String cmdHex;
 
+    /**
+     * isEs10 indicates that the current streaming APDU contains an ES10 command or it is a regular
+     * APDU. (As per spec SGP.22 V3.0, ES10 commands needs to be sent over command port of MEP-A1)
+     */
+    public final boolean isEs10;
+
     /** The parameters are defined as in GlobalPlatform Card Specification v.2.3. */
     ApduCommand(int channel, int cla, int ins, int p1, int p2, int p3, String cmdHex) {
         this.channel = channel;
@@ -52,11 +58,14 @@
         this.p2 = p2;
         this.p3 = p3;
         this.cmdHex = cmdHex;
+        // TODO: Currently ApduCommand is used for ES10 commands, so updating to true by default.
+        //  Modify it in case used for non ES10 commands in future.
+        this.isEs10 = true;
     }
 
     @Override
     public String toString() {
         return "ApduCommand(channel=" + channel + ", cla=" + cla + ", ins=" + ins + ", p1=" + p1
-                + ", p2=" + p2 + ", p3=" + p3 + ", cmd=" + cmdHex + ")";
+                + ", p2=" + p2 + ", p3=" + p3 + ", cmd=" + cmdHex + ", isEs10=" + isEs10 + ")";
     }
 }
diff --git a/src/java/com/android/internal/telephony/uicc/euicc/apdu/CloseLogicalChannelInvocation.java b/src/java/com/android/internal/telephony/uicc/euicc/apdu/CloseLogicalChannelInvocation.java
index 82ddb80..a69977b 100644
--- a/src/java/com/android/internal/telephony/uicc/euicc/apdu/CloseLogicalChannelInvocation.java
+++ b/src/java/com/android/internal/telephony/uicc/euicc/apdu/CloseLogicalChannelInvocation.java
@@ -43,7 +43,10 @@
     @Override
     protected void sendRequestMessage(Integer channel, Message msg) {
         Rlog.v(LOG_TAG, "Channel: " + channel);
-        mCi.iccCloseLogicalChannel(channel, msg);
+        // TODO: Currently CloseLogicalChannelInvocation is used from ApduSender for closing the
+        //  channel opened on ISD-R. Hence passing isEs10 as true by default.
+        //  Should modify the logic in future if the ApduSender is used with non ISD-R AID.
+        mCi.iccCloseLogicalChannel(channel, true /*isEs10*/, msg);
     }
 
     @Override
diff --git a/src/java/com/android/internal/telephony/uicc/euicc/apdu/TransmitApduLogicalChannelInvocation.java b/src/java/com/android/internal/telephony/uicc/euicc/apdu/TransmitApduLogicalChannelInvocation.java
index 09de54a..ca75beb 100644
--- a/src/java/com/android/internal/telephony/uicc/euicc/apdu/TransmitApduLogicalChannelInvocation.java
+++ b/src/java/com/android/internal/telephony/uicc/euicc/apdu/TransmitApduLogicalChannelInvocation.java
@@ -48,7 +48,8 @@
     protected void sendRequestMessage(ApduCommand command, Message msg) {
         Rlog.v(LOG_TAG, "Send: " + command);
         mCi.iccTransmitApduLogicalChannel(command.channel, command.cla | command.channel,
-                command.ins, command.p1, command.p2, command.p3, command.cmdHex, msg);
+                command.ins, command.p1, command.p2, command.p3, command.cmdHex, command.isEs10,
+                msg);
     }
 
     @Override
diff --git a/tests/telephonytests/assets/eccdata b/tests/telephonytests/assets/eccdata
index 4c5959b..bd9ff47 100644
--- a/tests/telephonytests/assets/eccdata
+++ b/tests/telephonytests/assets/eccdata
Binary files differ
diff --git a/tests/telephonytests/assets/eccdata_input.txt b/tests/telephonytests/assets/eccdata_input.txt
index 8dcfa75..6f8632c 100644
--- a/tests/telephonytests/assets/eccdata_input.txt
+++ b/tests/telephonytests/assets/eccdata_input.txt
@@ -10,6 +10,57 @@
     types: MOUNTAIN_RESCUE
     types: MIEC
     types: AIEC
+    routing: NORMAL
+    normal_routing_mncs: "05"
+  }
+  eccs {
+    phone_number: "888"
+    types: POLICE
+    types: AMBULANCE
+    types: FIRE
+    types: MARINE_GUARD
+    types: MOUNTAIN_RESCUE
+    types: MIEC
+    types: AIEC
+    routing: NORMAL
+    normal_routing_mncs: "45"
+    normal_routing_mncs: "47"
+    normal_routing_mncs: "05"
+  }
+  eccs {
+    phone_number: "654321"
+    types: POLICE
+    types: AMBULANCE
+    types: FIRE
+    types: MARINE_GUARD
+    types: MOUNTAIN_RESCUE
+    types: MIEC
+    types: AIEC
+    routing: EMERGENCY
+    normal_routing_mncs: ""
+  }
+  eccs {
+    phone_number: "7654321"
+    types: POLICE
+    types: AMBULANCE
+    types: FIRE
+    types: MARINE_GUARD
+    types: MOUNTAIN_RESCUE
+    types: MIEC
+    types: AIEC
+    routing: NORMAL
+    normal_routing_mncs: ""
+  }
+  eccs {
+    phone_number: "4321"
+    types: POLICE
+    types: AMBULANCE
+    types: FIRE
+    types: MARINE_GUARD
+    types: MOUNTAIN_RESCUE
+    types: MIEC
+    types: AIEC
+    routing: NORMAL
   }
   ecc_fallback: "911"
 }
diff --git a/tests/telephonytests/src/android/telephony/ims/ImsRegistrationTests.java b/tests/telephonytests/src/android/telephony/ims/ImsRegistrationTests.java
index af80bd7..56ce6bc 100644
--- a/tests/telephonytests/src/android/telephony/ims/ImsRegistrationTests.java
+++ b/tests/telephonytests/src/android/telephony/ims/ImsRegistrationTests.java
@@ -16,9 +16,13 @@
 
 package android.telephony.ims;
 
+import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK;
+import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_LTE;
+
 import static junit.framework.Assert.assertEquals;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -147,7 +151,33 @@
         ImsReasonInfo info = new ImsReasonInfo();
         mRegistration.onDeregistered(info);
 
-        verify(mCallback).onDeregistered(eq(info));
+        verify(mCallback).onDeregistered(eq(info), anyInt(), anyInt());
+    }
+
+    @SmallTest
+    @Test
+    public void testRegistrationCallbackOnDeregisteredWithSuggestedAction() throws RemoteException {
+        ImsReasonInfo info = new ImsReasonInfo();
+        mRegistration.onDeregistered(info,
+                SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK, REGISTRATION_TECH_LTE);
+
+        verify(mCallback).onDeregistered(eq(info),
+                eq(SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK), eq(REGISTRATION_TECH_LTE));
+    }
+
+    @SmallTest
+    @Test
+    public void testRegistrationCallbackOnDeregisteredWithRegistrationAttributes()
+            throws RemoteException {
+        ImsReasonInfo info = new ImsReasonInfo();
+        SipDetails details = new SipDetails.Builder(SipDetails.METHOD_REGISTER)
+                .setCSeq(1).setSipResponseCode(200, "OK")
+                .setSipResponseReasonHeader(10, "reasonText")
+                .setCallId("testCallId").build();
+
+        mRegistration.onDeregistered(info, details);
+
+        verify(mCallback).onDeregisteredWithDetails(eq(info), anyInt(), anyInt(), eq(details));
     }
 
     @SmallTest
@@ -218,10 +248,10 @@
 
         // The original callback that has been registered should get LTE tech in disconnected
         // message
-        verify(mCallback).onDeregistered(eq(info));
+        verify(mCallback).onDeregistered(eq(info), anyInt(), anyInt());
         // A callback that has just been registered should get NONE for tech in disconnected
         // message
-        verify(mCallback2).onDeregistered(eq(info));
+        verify(mCallback2).onDeregistered(eq(info), anyInt(), anyInt());
     }
 
     @SmallTest
@@ -231,7 +261,7 @@
 
         mRegistration.onDeregistered(info);
 
-        verify(mCallback).onDeregistered(eq(info));
+        verify(mCallback).onDeregistered(eq(info), anyInt(), anyInt());
         assertEquals(ImsRegistrationImplBase.REGISTRATION_TECH_NONE,
                 mRegBinder.getRegistrationTechnology());
     }
@@ -242,7 +272,7 @@
         mRegBinder.addRegistrationCallback(mCallback2);
         // Verify that if we have never set the registration state, we do not callback immediately
         // with onUnregistered.
-        verify(mCallback2, never()).onDeregistered(any(ImsReasonInfo.class));
+        verify(mCallback2, never()).onDeregistered(any(ImsReasonInfo.class), anyInt(), anyInt());
     }
 
     @SmallTest
diff --git a/tests/telephonytests/src/android/telephony/ims/MmTelFeatureTests.java b/tests/telephonytests/src/android/telephony/ims/MmTelFeatureTests.java
index 5af871c..b002b35 100644
--- a/tests/telephonytests/src/android/telephony/ims/MmTelFeatureTests.java
+++ b/tests/telephonytests/src/android/telephony/ims/MmTelFeatureTests.java
@@ -25,6 +25,7 @@
 import static org.mockito.Mockito.verify;
 
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -151,13 +152,27 @@
 
         mFeature.incomingCall(session);
         ArgumentCaptor<IImsCallSession> captor = ArgumentCaptor.forClass(IImsCallSession.class);
-        verify(mListener).onIncomingCall(captor.capture(), any());
+        verify(mListener).onIncomingCall(captor.capture(), eq(null), any());
 
         assertEquals(sessionBinder, captor.getValue());
     }
 
     @SmallTest
     @Test
+    public void testNewIncomingCallReturnListener() throws Exception {
+        IImsCallSession sessionBinder = Mockito.mock(IImsCallSession.class);
+        ImsCallSessionImplBase session = new ImsCallSessionImplBase();
+        session.setServiceImpl(sessionBinder);
+        String callId = "callID";
+        Bundle extra = new Bundle();
+        mFeature.incomingCall(session, callId, extra);
+        ArgumentCaptor<IImsCallSession> captor = ArgumentCaptor.forClass(IImsCallSession.class);
+        verify(mListener).onIncomingCall(captor.capture(), eq(callId), eq(extra));
+        assertEquals(sessionBinder, captor.getValue());
+    }
+
+    @SmallTest
+    @Test
     public void testSetTtyMessageMessenger() throws Exception {
         Message resultMessage = Message.obtain(mHandler, TEST_TTY_RESULT);
         resultMessage.replyTo = mHandlerMessenger;
diff --git a/tests/telephonytests/src/android/telephony/ims/TestMmTelFeature.java b/tests/telephonytests/src/android/telephony/ims/TestMmTelFeature.java
index 9ee1e30..1269cc8 100644
--- a/tests/telephonytests/src/android/telephony/ims/TestMmTelFeature.java
+++ b/tests/telephonytests/src/android/telephony/ims/TestMmTelFeature.java
@@ -64,6 +64,11 @@
         notifyIncomingCall(c, new Bundle());
     }
 
+    public ImsCallSessionListener incomingCall(
+            ImsCallSessionImplBase c, String callId, Bundle extra) {
+        return notifyIncomingCall(c, callId, extra);
+    }
+
     @Override
     public ImsCallProfile createCallProfile(int callSessionType, int callType) {
         return super.createCallProfile(callSessionType, callType);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CallStateTest.java b/tests/telephonytests/src/com/android/internal/telephony/CallStateTest.java
new file mode 100644
index 0000000..4e319a1
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/CallStateTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2019 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 android.os.Parcel;
+import android.telephony.CallQuality;
+import android.telephony.CallState;
+import android.telephony.PreciseCallState;
+import android.telephony.TelephonyManager;
+import android.telephony.ims.ImsCallProfile;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Simple GTS test verifying the parceling and unparceling of CallAttributes.
+ */
+public class CallStateTest extends AndroidTestCase {
+
+
+    @SmallTest
+    public void testParcelUnparcelPreciseCallState() {
+        CallState data = new CallState.Builder(PreciseCallState.PRECISE_CALL_STATE_INCOMING)
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_LTE)
+                .setCallQuality(null)
+                .setCallClassification(CallState.CALL_CLASSIFICATION_RINGING)
+                .setImsCallSessionId("1")
+                .setImsCallServiceType(ImsCallProfile.SERVICE_TYPE_NONE)
+                .setImsCallType(ImsCallProfile.CALL_TYPE_NONE).build();
+
+        Parcel parcel = Parcel.obtain();
+        data.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        CallState unparceledData = CallState.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+
+        assertEquals("PreciseCallState is not equal after parceled/unparceled",
+                data.getCallState(),
+                unparceledData.getCallState());
+    }
+
+    @SmallTest
+    public void testParcelUnparcelCallQuality() {
+        CallQuality quality = new CallQuality();
+        CallState data = new CallState.Builder(PreciseCallState.PRECISE_CALL_STATE_IDLE)
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_LTE)
+                .setCallQuality(null)
+                .setCallClassification(CallState.CALL_CLASSIFICATION_FOREGROUND)
+                .setImsCallSessionId(null)
+                .setImsCallServiceType(ImsCallProfile.SERVICE_TYPE_NONE)
+                .setImsCallType(ImsCallProfile.CALL_TYPE_NONE).build();
+
+
+        Parcel parcel = Parcel.obtain();
+        data.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        CallState unparceledData = CallState.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+
+        assertNull(unparceledData.getCallQuality());
+
+        data = new CallState.Builder(PreciseCallState.PRECISE_CALL_STATE_IDLE)
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_LTE)
+                .setCallQuality(quality)
+                .setCallClassification(CallState.CALL_CLASSIFICATION_FOREGROUND)
+                .setImsCallSessionId(null)
+                .setImsCallServiceType(ImsCallProfile.SERVICE_TYPE_NONE)
+                .setImsCallType(ImsCallProfile.CALL_TYPE_NONE).build();
+
+
+        parcel = Parcel.obtain();
+        data.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        unparceledData = CallState.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+
+        assertEquals("CallQuality is not equal after parceled/unparceled",
+                data.getCallQuality(),
+                unparceledData.getCallQuality());
+    }
+
+    @SmallTest
+    public void testParcelUnparcelNetworkTypeAndClassification() {
+        CallQuality quality = new CallQuality();
+        CallState data = new CallState.Builder(PreciseCallState.PRECISE_CALL_STATE_DIALING)
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_LTE)
+                .setCallQuality(null)
+                .setCallClassification(CallState.CALL_CLASSIFICATION_FOREGROUND)
+                .setImsCallSessionId("3")
+                .setImsCallServiceType(ImsCallProfile.SERVICE_TYPE_NONE)
+                .setImsCallType(ImsCallProfile.CALL_TYPE_NONE).build();
+
+        Parcel parcel = Parcel.obtain();
+        data.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        CallState unparceledData = CallState.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+
+        assertEquals("NetworkType is not equal after parceled/unparceled",
+                data.getNetworkType(),
+                unparceledData.getNetworkType());
+        assertEquals("Call classification is not equal after parceled/unparceled",
+                data.getCallClassification(),
+                unparceledData.getCallClassification());
+    }
+
+    @Test
+    public void testParcelUnparcelImsCallInfo() {
+        CallQuality quality = new CallQuality();
+        CallState data = new CallState.Builder(PreciseCallState.PRECISE_CALL_STATE_DIALING)
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_LTE)
+                .setCallQuality(null)
+                .setCallClassification(CallState.CALL_CLASSIFICATION_FOREGROUND)
+                .setImsCallSessionId(null)
+                .setImsCallServiceType(ImsCallProfile.SERVICE_TYPE_NORMAL)
+                .setImsCallType(ImsCallProfile.CALL_TYPE_VOICE).build();
+
+        Parcel parcel = Parcel.obtain();
+        data.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        CallState unparceledData = CallState.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+
+        assertNull(unparceledData.getImsCallSessionId());
+
+        assertEquals("Ims call service type is not equal after parceled/unparceled",
+                data.getImsCallServiceType(),
+                unparceledData.getImsCallServiceType());
+
+        assertEquals("Ims call type is not equal after parceled/unparceled",
+                data.getImsCallType(),
+                unparceledData.getImsCallType());
+
+        data = new CallState.Builder(PreciseCallState.PRECISE_CALL_STATE_ACTIVE)
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_LTE)
+                .setCallQuality(quality)
+                .setCallClassification(CallState.CALL_CLASSIFICATION_FOREGROUND)
+                .setImsCallSessionId("2")
+                .setImsCallServiceType(ImsCallProfile.SERVICE_TYPE_NORMAL)
+                .setImsCallType(ImsCallProfile.CALL_TYPE_VT).build();
+
+        parcel = Parcel.obtain();
+        data.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        unparceledData = CallState.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+
+        assertEquals("Ims call session ID is not equal after parceled/unparceled",
+                data.getImsCallSessionId(),
+                unparceledData.getImsCallSessionId());
+
+        assertEquals("Ims call service type is not equal after parceled/unparceled",
+                data.getImsCallServiceType(),
+                unparceledData.getImsCallServiceType());
+
+        assertEquals("Ims call type is not equal after parceled/unparceled",
+                data.getImsCallType(),
+                unparceledData.getImsCallType());
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CallWaitingControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/CallWaitingControllerTest.java
new file mode 100644
index 0000000..eb18adb
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/CallWaitingControllerTest.java
@@ -0,0 +1,477 @@
+/*
+ * Copyright (C) 2022 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 android.telephony.CarrierConfigManager.ImsSs.CALL_WAITING_SYNC_FIRST_CHANGE;
+import static android.telephony.CarrierConfigManager.ImsSs.CALL_WAITING_SYNC_FIRST_POWER_UP;
+import static android.telephony.CarrierConfigManager.ImsSs.CALL_WAITING_SYNC_IMS_ONLY;
+import static android.telephony.CarrierConfigManager.ImsSs.CALL_WAITING_SYNC_NONE;
+import static android.telephony.CarrierConfigManager.ImsSs.CALL_WAITING_SYNC_USER_CHANGE;
+import static android.telephony.CarrierConfigManager.ImsSs.KEY_TERMINAL_BASED_CALL_WAITING_DEFAULT_ENABLED_BOOL;
+import static android.telephony.CarrierConfigManager.ImsSs.KEY_TERMINAL_BASED_CALL_WAITING_SYNC_TYPE_INT;
+import static android.telephony.CarrierConfigManager.ImsSs.KEY_UT_TERMINAL_BASED_SERVICES_INT_ARRAY;
+import static android.telephony.CarrierConfigManager.ImsSs.SUPPLEMENTARY_SERVICE_CW;
+
+import static com.android.internal.telephony.CallWaitingController.KEY_CS_SYNC;
+import static com.android.internal.telephony.CallWaitingController.KEY_STATE;
+import static com.android.internal.telephony.CallWaitingController.KEY_SUB_ID;
+import static com.android.internal.telephony.CallWaitingController.PREFERENCE_TBCW;
+import static com.android.internal.telephony.CallWaitingController.TERMINAL_BASED_ACTIVATED;
+import static com.android.internal.telephony.CallWaitingController.TERMINAL_BASED_NOT_ACTIVATED;
+import static com.android.internal.telephony.CallWaitingController.TERMINAL_BASED_NOT_SUPPORTED;
+import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_DATA;
+import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_NONE;
+import static com.android.internal.telephony.CommandsInterface.SERVICE_CLASS_VOICE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.AsyncResult;
+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 org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class CallWaitingControllerTest extends TelephonyTest {
+    private static final int FAKE_SUB_ID = 1;
+
+    private static final int GET_DONE = 1;
+
+    private CallWaitingController mCWC;
+    private GetTestHandler mHandler;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(this.getClass().getSimpleName());
+        mSimulatedCommands.setRadioPower(true, null);
+        mPhone.mCi = this.mSimulatedCommands;
+        doReturn(FAKE_SUB_ID).when(mPhone).getSubId();
+
+        mCWC = new CallWaitingController(mPhone);
+        logd("CallWaitingController initiated, waiting for Power on");
+        /* Make sure radio state is power on before dial.
+         * When radio state changed from off to on, CallTracker
+         * will poll result from RIL. Avoid dialing triggered at the same*/
+        processAllMessages();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mCWC = null;
+        super.tearDown();
+    }
+
+    @Test
+    @SmallTest
+    public void testSetTerminalBasedCallWaitingSupported() {
+        mCWC.setTerminalBasedCallWaitingSupported(true);
+        PersistableBundle bundle = getConfigBundle(true, CALL_WAITING_SYNC_NONE, true);
+        when(mCarrierConfigManager.getConfigForSubId(anyInt(), any())).thenReturn(bundle);
+        mCWC.updateCarrierConfig(FAKE_SUB_ID, true);
+
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_ACTIVATED);
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_ACTIVATED);
+
+        mCWC.setTerminalBasedCallWaitingSupported(false);
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_NOT_SUPPORTED);
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_NOT_SUPPORTED);
+    }
+
+    @Test
+    @SmallTest
+    public void testInitialize() {
+        mCWC.setTerminalBasedCallWaitingSupported(false);
+        setPreference(mPhone.getPhoneId(), FAKE_SUB_ID,
+                TERMINAL_BASED_ACTIVATED, CALL_WAITING_SYNC_NONE);
+        PersistableBundle bundle = getConfigBundle(true, CALL_WAITING_SYNC_NONE, true);
+        doReturn(bundle).when(mCarrierConfigManager).getConfigForSubId(anyInt(), any());
+        mCWC.setTerminalBasedCallWaitingSupported(true);
+
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_ACTIVATED);
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_ACTIVATED);
+
+        mCWC.setTerminalBasedCallWaitingSupported(false);
+        setPreference(mPhone.getPhoneId(), FAKE_SUB_ID,
+                TERMINAL_BASED_NOT_ACTIVATED, CALL_WAITING_SYNC_NONE);
+        mCWC.setTerminalBasedCallWaitingSupported(true);
+
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_NOT_ACTIVATED);
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_NOT_ACTIVATED);
+
+        mCWC.setTerminalBasedCallWaitingSupported(false);
+        bundle = getConfigBundle(false, CALL_WAITING_SYNC_NONE, false);
+        doReturn(bundle).when(mCarrierConfigManager).getConfigForSubId(anyInt(), any());
+        mCWC.setTerminalBasedCallWaitingSupported(true);
+
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_NOT_SUPPORTED);
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_NOT_SUPPORTED);
+    }
+
+    @Test
+    @SmallTest
+    public void testCarrierConfigChanged() {
+        mCWC.setTerminalBasedCallWaitingSupported(true);
+        PersistableBundle bundle = getConfigBundle(true, CALL_WAITING_SYNC_NONE, true);
+        when(mCarrierConfigManager.getConfigForSubId(anyInt(), any())).thenReturn(bundle);
+        mCWC.updateCarrierConfig(FAKE_SUB_ID, true);
+
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_ACTIVATED);
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_ACTIVATED);
+
+        bundle = getConfigBundle(false, CALL_WAITING_SYNC_NONE, true);
+        when(mCarrierConfigManager.getConfigForSubId(anyInt(), any())).thenReturn(bundle);
+        mCWC.updateCarrierConfig(FAKE_SUB_ID, false);
+
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_NOT_SUPPORTED);
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_NOT_SUPPORTED);
+    }
+
+
+    private static class GetTestHandler extends Handler {
+        public int[] resp;
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case GET_DONE:
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    resp = (int[]) ar.result;
+                    break;
+                default:
+            }
+        }
+
+        public void reset() {
+            resp = null;
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testGetCallWaitingSyncNone() {
+        mCWC.setTerminalBasedCallWaitingSupported(false);
+        assertFalse(mCWC.getCallWaiting(null));
+
+        mCWC.setTerminalBasedCallWaitingSupported(true);
+        PersistableBundle bundle = getConfigBundle(true, CALL_WAITING_SYNC_NONE, true);
+        when(mCarrierConfigManager.getConfigForSubId(anyInt(), any())).thenReturn(bundle);
+        mCWC.updateCarrierConfig(FAKE_SUB_ID, true);
+
+        mHandler = new GetTestHandler();
+
+        assertTrue(mCWC.setCallWaiting(true, SERVICE_CLASS_VOICE, null));
+        mTestableLooper.processAllMessages();
+
+        assertTrue(mCWC.getCallWaiting(mHandler.obtainMessage(GET_DONE)));
+        mTestableLooper.processAllMessages();
+
+        assertNotNull(mHandler.resp);
+        assertEquals(2, mHandler.resp.length);
+        assertEquals(TERMINAL_BASED_ACTIVATED, mHandler.resp[0]);
+        assertEquals(SERVICE_CLASS_VOICE, mHandler.resp[1]);
+
+        mHandler.reset();
+
+        assertTrue(mCWC.setCallWaiting(false, SERVICE_CLASS_VOICE, null));
+        mTestableLooper.processAllMessages();
+
+        assertTrue(mCWC.getCallWaiting(mHandler.obtainMessage(GET_DONE)));
+        mTestableLooper.processAllMessages();
+
+        assertNotNull(mHandler.resp);
+        assertEquals(2, mHandler.resp.length);
+        assertEquals(TERMINAL_BASED_NOT_ACTIVATED, mHandler.resp[0]);
+        assertEquals(SERVICE_CLASS_NONE, mHandler.resp[1]);
+    }
+
+    @Test
+    @SmallTest
+    public void testSetCallWaitingSyncNone() {
+        mCWC.setTerminalBasedCallWaitingSupported(false);
+        assertFalse(mCWC.setCallWaiting(true, SERVICE_CLASS_VOICE, null));
+
+        mCWC.setTerminalBasedCallWaitingSupported(true);
+        PersistableBundle bundle = getConfigBundle(true, CALL_WAITING_SYNC_NONE, true);
+        when(mCarrierConfigManager.getConfigForSubId(anyInt(), any())).thenReturn(bundle);
+        mCWC.updateCarrierConfig(FAKE_SUB_ID, true);
+
+        assertTrue(mCWC.setCallWaiting(true, SERVICE_CLASS_VOICE, null));
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_ACTIVATED);
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_ACTIVATED);
+        assertTrue(retrieveStatePreference(mPhone.getSubId()) == TERMINAL_BASED_ACTIVATED);
+
+        assertTrue(mCWC.setCallWaiting(false, SERVICE_CLASS_VOICE | SERVICE_CLASS_DATA, null));
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_NOT_ACTIVATED);
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_NOT_ACTIVATED);
+        assertTrue(retrieveStatePreference(mPhone.getSubId()) == TERMINAL_BASED_NOT_ACTIVATED);
+
+        assertFalse(mCWC.setCallWaiting(true, SERVICE_CLASS_DATA, null));
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_NOT_ACTIVATED);
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_NOT_ACTIVATED);
+        assertTrue(retrieveStatePreference(mPhone.getSubId()) == TERMINAL_BASED_NOT_ACTIVATED);
+
+        assertFalse(mCWC.setCallWaiting(true, SERVICE_CLASS_NONE, null));
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_NOT_ACTIVATED);
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_NOT_ACTIVATED);
+        assertTrue(retrieveStatePreference(mPhone.getSubId()) == TERMINAL_BASED_NOT_ACTIVATED);
+    }
+
+    @Test
+    @SmallTest
+    public void testSyncUserChange() {
+        mCWC.setTerminalBasedCallWaitingSupported(false);
+        setPreference(mPhone.getPhoneId(), FAKE_SUB_ID,
+                TERMINAL_BASED_ACTIVATED, CALL_WAITING_SYNC_USER_CHANGE);
+        mCWC.setTerminalBasedCallWaitingSupported(true);
+        PersistableBundle bundle = getConfigBundle(true, CALL_WAITING_SYNC_USER_CHANGE, true);
+        when(mCarrierConfigManager.getConfigForSubId(anyInt(), any())).thenReturn(bundle);
+        mCWC.updateCarrierConfig(FAKE_SUB_ID, true);
+
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_ACTIVATED);
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_ACTIVATED);
+        assertTrue(retrieveStatePreference(mPhone.getSubId()) == TERMINAL_BASED_ACTIVATED);
+
+        mHandler = new GetTestHandler();
+
+        mSimulatedCommands.setCallWaiting(false, SERVICE_CLASS_VOICE, null);
+
+        assertTrue(mCWC.getCallWaiting(mHandler.obtainMessage(GET_DONE)));
+        mTestableLooper.processAllMessages();
+
+        assertNotNull(mHandler.resp);
+        assertEquals(2, mHandler.resp.length);
+        assertEquals(TERMINAL_BASED_NOT_ACTIVATED, mHandler.resp[0]);
+        assertEquals(SERVICE_CLASS_NONE, mHandler.resp[1]);
+
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_NOT_ACTIVATED);
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_NOT_ACTIVATED);
+        assertTrue(retrieveStatePreference(mPhone.getSubId()) == TERMINAL_BASED_NOT_ACTIVATED);
+
+        mHandler.reset();
+
+        mSimulatedCommands.setCallWaiting(true, SERVICE_CLASS_VOICE, null);
+
+        assertTrue(mCWC.getCallWaiting(mHandler.obtainMessage(GET_DONE)));
+        mTestableLooper.processAllMessages();
+
+        assertNotNull(mHandler.resp);
+        assertEquals(2, mHandler.resp.length);
+        assertEquals(TERMINAL_BASED_ACTIVATED, mHandler.resp[0]);
+        assertEquals(SERVICE_CLASS_VOICE, mHandler.resp[1]);
+
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_ACTIVATED);
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_ACTIVATED);
+        assertTrue(retrieveStatePreference(mPhone.getSubId()) == TERMINAL_BASED_ACTIVATED);
+
+        mHandler.reset();
+
+        assertTrue(mCWC.setCallWaiting(false, SERVICE_CLASS_VOICE, null));
+        mTestableLooper.processAllMessages();
+
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_NOT_ACTIVATED);
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_NOT_ACTIVATED);
+        assertTrue(retrieveStatePreference(mPhone.getSubId()) == TERMINAL_BASED_NOT_ACTIVATED);
+    }
+
+    @Test
+    @SmallTest
+    public void testSyncFirstPowerUp() {
+        mCWC.setTerminalBasedCallWaitingSupported(false);
+        setPreference(mPhone.getPhoneId(), FAKE_SUB_ID,
+                TERMINAL_BASED_NOT_ACTIVATED, CALL_WAITING_SYNC_FIRST_POWER_UP);
+        mCWC.setTerminalBasedCallWaitingSupported(true);
+        PersistableBundle bundle = getConfigBundle(true, CALL_WAITING_SYNC_FIRST_POWER_UP, true);
+        when(mCarrierConfigManager.getConfigForSubId(anyInt(), any())).thenReturn(bundle);
+        mCWC.updateCarrierConfig(FAKE_SUB_ID, true);
+        assertFalse(mCWC.getSyncState());
+
+        mCWC.notifyRegisteredToNetwork();
+        mTestableLooper.processAllMessages();
+
+        assertTrue(mCWC.getSyncState());
+    }
+
+    @Test
+    @SmallTest
+    public void testSyncFirstChange() {
+        mCWC.setTerminalBasedCallWaitingSupported(false);
+        setPreference(mPhone.getPhoneId(), FAKE_SUB_ID,
+                TERMINAL_BASED_NOT_ACTIVATED, CALL_WAITING_SYNC_FIRST_CHANGE);
+        mCWC.setTerminalBasedCallWaitingSupported(true);
+        PersistableBundle bundle = getConfigBundle(true, CALL_WAITING_SYNC_FIRST_CHANGE, true);
+        when(mCarrierConfigManager.getConfigForSubId(anyInt(), any())).thenReturn(bundle);
+        mCWC.updateCarrierConfig(FAKE_SUB_ID, true);
+        mCWC.setImsRegistrationState(false);
+
+        assertFalse(mCWC.getSyncState());
+
+        mSimulatedCommands.setCallWaiting(false, SERVICE_CLASS_VOICE, null);
+        mCWC.getCallWaiting(null);
+        mTestableLooper.processAllMessages();
+
+        assertFalse(mCWC.getSyncState());
+
+        mSimulatedCommands.setCallWaiting(true, SERVICE_CLASS_VOICE, null);
+        mCWC.getCallWaiting(null);
+        mTestableLooper.processAllMessages();
+
+        assertTrue(mCWC.getSyncState());
+
+        assertTrue(mCWC.setCallWaiting(true, SERVICE_CLASS_VOICE, null));
+        mTestableLooper.processAllMessages();
+
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_ACTIVATED);
+        assertTrue(mSimulatedCommands.mCallWaitActivated);
+
+        assertTrue(mCWC.setCallWaiting(false, SERVICE_CLASS_VOICE, null));
+        mTestableLooper.processAllMessages();
+
+        // Local setting changed, but no change in CS network.
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_NOT_ACTIVATED);
+        assertTrue(mSimulatedCommands.mCallWaitActivated);
+    }
+
+    @Test
+    @SmallTest
+    public void testSyncImsOnly() {
+        mCWC.setTerminalBasedCallWaitingSupported(false);
+        setPreference(mPhone.getPhoneId(), FAKE_SUB_ID,
+                TERMINAL_BASED_ACTIVATED, CALL_WAITING_SYNC_IMS_ONLY);
+        mCWC.setTerminalBasedCallWaitingSupported(true);
+        PersistableBundle bundle = getConfigBundle(true, CALL_WAITING_SYNC_IMS_ONLY, true);
+        when(mCarrierConfigManager.getConfigForSubId(anyInt(), any())).thenReturn(bundle);
+        mCWC.updateCarrierConfig(FAKE_SUB_ID, true);
+
+        mSimulatedCommands.setCallWaiting(false, SERVICE_CLASS_VOICE, null);
+
+        // IMS is registered
+        mCWC.setImsRegistrationState(true);
+
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_ACTIVATED);
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_ACTIVATED);
+        assertTrue(retrieveStatePreference(mPhone.getSubId()) == TERMINAL_BASED_ACTIVATED);
+
+        mHandler = new GetTestHandler();
+
+        assertTrue(mCWC.getCallWaiting(mHandler.obtainMessage(GET_DONE)));
+        mTestableLooper.processAllMessages();
+
+        // result carries the service state from IMS service
+        assertNotNull(mHandler.resp);
+        assertEquals(2, mHandler.resp.length);
+        assertEquals(TERMINAL_BASED_ACTIVATED, mHandler.resp[0]);
+        assertEquals(SERVICE_CLASS_VOICE, mHandler.resp[1]);
+
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_ACTIVATED);
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_ACTIVATED);
+        assertTrue(retrieveStatePreference(mPhone.getSubId()) == TERMINAL_BASED_ACTIVATED);
+
+        mHandler.reset();
+
+        // IMS is not registered
+        mCWC.setImsRegistrationState(false);
+
+        assertTrue(mCWC.getCallWaiting(mHandler.obtainMessage(GET_DONE)));
+        mTestableLooper.processAllMessages();
+
+        // result carries the service state from CS
+        assertNotNull(mHandler.resp);
+        assertEquals(2, mHandler.resp.length);
+        assertEquals(TERMINAL_BASED_NOT_ACTIVATED, mHandler.resp[0]);
+        assertEquals(SERVICE_CLASS_NONE, mHandler.resp[1]);
+
+        // service state not synchronized between CS and IMS
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_ACTIVATED);
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_NOT_SUPPORTED);
+        assertTrue(retrieveStatePreference(mPhone.getSubId()) == TERMINAL_BASED_ACTIVATED);
+
+        mHandler.reset();
+
+        // IMS is registered
+        mCWC.setImsRegistrationState(true);
+
+        assertTrue(mCWC.setCallWaiting(false, SERVICE_CLASS_VOICE, null));
+        mTestableLooper.processAllMessages();
+
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_NOT_ACTIVATED);
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_NOT_ACTIVATED);
+        assertTrue(retrieveStatePreference(mPhone.getSubId()) == TERMINAL_BASED_NOT_ACTIVATED);
+
+        // IMS is not registered
+        mCWC.setImsRegistrationState(false);
+
+        assertTrue(mCWC.setCallWaiting(true, SERVICE_CLASS_VOICE, null));
+        mTestableLooper.processAllMessages();
+
+        assertTrue(mCWC.getCallWaiting(mHandler.obtainMessage(GET_DONE)));
+        mTestableLooper.processAllMessages();
+
+        // result carries the service state from CS
+        assertNotNull(mHandler.resp);
+        assertEquals(2, mHandler.resp.length);
+        assertEquals(TERMINAL_BASED_ACTIVATED, mHandler.resp[0]);
+        assertEquals(SERVICE_CLASS_VOICE, mHandler.resp[1]);
+
+        // service state not synchronized between CS and IMS
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(false) == TERMINAL_BASED_NOT_ACTIVATED);
+        assertTrue(mCWC.getTerminalBasedCallWaitingState(true) == TERMINAL_BASED_NOT_SUPPORTED);
+        assertTrue(retrieveStatePreference(mPhone.getSubId()) == TERMINAL_BASED_NOT_ACTIVATED);
+    }
+
+    private PersistableBundle getConfigBundle(boolean provisioned,
+            int preference, boolean defaultState) {
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putIntArray(KEY_UT_TERMINAL_BASED_SERVICES_INT_ARRAY,
+                provisioned ? new int[] { SUPPLEMENTARY_SERVICE_CW } : new int[] { });
+        bundle.putInt(KEY_TERMINAL_BASED_CALL_WAITING_SYNC_TYPE_INT, preference);
+        bundle.putBoolean(KEY_TERMINAL_BASED_CALL_WAITING_DEFAULT_ENABLED_BOOL, defaultState);
+        return bundle;
+    }
+
+    private int retrieveStatePreference(int subId) {
+        SharedPreferences sp =
+                mContext.getSharedPreferences(PREFERENCE_TBCW, Context.MODE_PRIVATE);
+        return sp.getInt(KEY_STATE + subId, TERMINAL_BASED_NOT_SUPPORTED);
+    }
+
+    private void setPreference(int phoneId, int subId, int state, int syncPreference) {
+        SharedPreferences sp =
+                mContext.getSharedPreferences(PREFERENCE_TBCW, Context.MODE_PRIVATE);
+
+        SharedPreferences.Editor editor = sp.edit();
+        editor.putInt(KEY_SUB_ID + phoneId, subId);
+        editor.putInt(KEY_STATE + subId, state);
+        editor.putInt(KEY_CS_SYNC + phoneId, syncPreference);
+        editor.apply();
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellBroadcastConfigTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellBroadcastConfigTrackerTest.java
new file mode 100644
index 0000000..40be490
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellBroadcastConfigTrackerTest.java
@@ -0,0 +1,522 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import static com.android.internal.telephony.CellBroadcastConfigTracker.mergeRangesAsNeeded;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.os.AsyncResult;
+import android.os.Message;
+import android.telephony.CellBroadcastIdRange;
+import android.telephony.SmsCbMessage;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
+import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public final class CellBroadcastConfigTrackerTest extends TelephonyTest {
+
+    private CommandsInterface mSpyCi;
+    private CellBroadcastConfigTracker mTracker;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        mSpyCi = spy(mSimulatedCommands);
+        mPhone = new GsmCdmaPhone(mContext, mSpyCi, mNotifier, true, 0,
+            PhoneConstants.PHONE_TYPE_GSM, mTelephonyComponentFactory, (c, p) -> mImsManager);
+        mTracker = CellBroadcastConfigTracker.make(mPhone, mPhone, true);
+        mPhone.mCellBroadcastConfigTracker = mTracker;
+        processAllMessages();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mPhone.removeCallbacksAndMessages(null);
+        mPhone = null;
+        super.tearDown();
+    }
+
+    @Test
+    public void testSetCellBroadcastIdRangesSuccess() throws Exception {
+        final int[][] channelValues = {
+            {0, 999}, {1000, 1003}, {1004, 0x0FFF}, {0x1000, 0x10FF}, {0x1100, 0x112F},
+            {0x1130, 0x1900}, {0x1901, 0x9FFF}, {0xA000, 0xFFFE}, {0xFFFF, 0xFFFF}};
+        List<CellBroadcastIdRange> ranges = new ArrayList<>();
+        for (int i = 0; i < channelValues.length; i++) {
+            ranges.add(new CellBroadcastIdRange(channelValues[i][0], channelValues[i][1],
+                    SmsCbMessage.MESSAGE_FORMAT_3GPP, i > 0 ? true : false));
+        }
+
+        List<SmsBroadcastConfigInfo> gsmConfigs = new ArrayList<>();
+        gsmConfigs.add(new SmsBroadcastConfigInfo(0, 999, 0, 255, false));
+        gsmConfigs.add(new SmsBroadcastConfigInfo(1000, 0xFFFF, 0, 255, true));
+
+        ArgumentCaptor<SmsBroadcastConfigInfo[]> gsmCaptor = ArgumentCaptor.forClass(
+                SmsBroadcastConfigInfo[].class);
+        ArgumentCaptor<Message> msgCaptor = ArgumentCaptor.forClass(Message.class);
+
+        mockCommandInterface();
+
+        mPhone.setCellBroadcastIdRanges(ranges, r -> assertTrue(
+                TelephonyManager.CELL_BROADCAST_RESULT_SUCCESS == r));
+        processAllMessages();
+
+        verify(mSpyCi, times(1)).setGsmBroadcastConfig(gsmCaptor.capture(), msgCaptor.capture());
+        List<SmsBroadcastConfigInfo> gsmArgs = Arrays.asList(
+                (SmsBroadcastConfigInfo[]) gsmCaptor.getValue());
+        assertEquals(gsmConfigs, gsmArgs);
+
+        Message msg = msgCaptor.getValue();
+        assertNotNull(msg);
+        AsyncResult.forMessage(msg);
+        msg.sendToTarget();
+        processAllMessages();
+
+        verify(mSpyCi, times(1)).setGsmBroadcastActivation(eq(true), msgCaptor.capture());
+
+        msg = msgCaptor.getValue();
+        assertNotNull(msg);
+        AsyncResult.forMessage(msg);
+        msg.sendToTarget();
+        processAllMessages();
+
+        verify(mSpyCi, never()).setCdmaBroadcastConfig(any(), any());
+        verify(mSpyCi, never()).setCdmaBroadcastActivation(anyBoolean(), any());
+
+        assertEquals(mPhone.getCellBroadcastIdRanges(), mergeRangesAsNeeded(ranges));
+
+        //Verify to set cdma config and activate, but no more for gsm as no change
+        for (int i = 0; i < channelValues.length; i++) {
+            ranges.add(new CellBroadcastIdRange(channelValues[i][0], channelValues[i][1],
+                    SmsCbMessage.MESSAGE_FORMAT_3GPP2, i > 0 ? true : false));
+        }
+        List<CdmaSmsBroadcastConfigInfo> cdmaConfigs = new ArrayList<>();
+        cdmaConfigs.add(new CdmaSmsBroadcastConfigInfo(0, 999, 1, false));
+        cdmaConfigs.add(new CdmaSmsBroadcastConfigInfo(1000, 0xFFFF, 1, true));
+        ArgumentCaptor<CdmaSmsBroadcastConfigInfo[]> cdmaCaptor = ArgumentCaptor.forClass(
+                CdmaSmsBroadcastConfigInfo[].class);
+
+        mPhone.setCellBroadcastIdRanges(ranges, r -> assertTrue(
+                TelephonyManager.CELL_BROADCAST_RESULT_SUCCESS == r));
+        processAllMessages();
+
+        verify(mSpyCi, times(1)).setGsmBroadcastConfig(any(), any());
+        verify(mSpyCi, times(1)).setCdmaBroadcastConfig(cdmaCaptor.capture(), msgCaptor.capture());
+        List<CdmaSmsBroadcastConfigInfo> cdmaArgs = Arrays.asList(
+                (CdmaSmsBroadcastConfigInfo[]) cdmaCaptor.getValue());
+        assertEquals(cdmaConfigs, cdmaArgs);
+
+        msg = msgCaptor.getValue();
+        assertNotNull(msg);
+        AsyncResult.forMessage(msg);
+        msg.sendToTarget();
+        processAllMessages();
+
+        verify(mSpyCi, times(1)).setGsmBroadcastActivation(anyBoolean(), any());
+        verify(mSpyCi, times(1)).setCdmaBroadcastActivation(eq(true), msgCaptor.capture());
+
+        msg = msgCaptor.getValue();
+        assertNotNull(msg);
+        AsyncResult.forMessage(msg);
+        msg.sendToTarget();
+        processAllMessages();
+
+        assertEquals(mPhone.getCellBroadcastIdRanges(), mergeRangesAsNeeded(ranges));
+
+        // Verify not to set cdma or gsm config as the config is not changed
+        mPhone.setCellBroadcastIdRanges(ranges, r -> assertTrue(
+                TelephonyManager.CELL_BROADCAST_RESULT_SUCCESS == r));
+        processAllMessages();
+
+        verify(mSpyCi, times(1)).setCdmaBroadcastConfig(any(), any());
+        verify(mSpyCi, times(1)).setCdmaBroadcastActivation(anyBoolean(), any());
+        verify(mSpyCi, times(1)).setGsmBroadcastConfig(any(), any());
+        verify(mSpyCi, times(1)).setGsmBroadcastActivation(anyBoolean(), any());
+
+        assertEquals(mPhone.getCellBroadcastIdRanges(), mergeRangesAsNeeded(ranges));
+
+        // Verify to reset ranges with empty ranges list
+        mPhone.setCellBroadcastIdRanges(new ArrayList<>(), r -> assertTrue(
+                TelephonyManager.CELL_BROADCAST_RESULT_SUCCESS == r));
+
+        processAllMessages();
+
+        verify(mSpyCi, times(2)).setGsmBroadcastConfig(gsmCaptor.capture(), msgCaptor.capture());
+        assertEquals(0, ((SmsBroadcastConfigInfo[]) gsmCaptor.getValue()).length);
+
+        msg = msgCaptor.getValue();
+        assertNotNull(msg);
+        AsyncResult.forMessage(msg);
+        msg.sendToTarget();
+        processAllMessages();
+
+        // Verify to deavtivate gsm broadcast on empty ranges
+        verify(mSpyCi, times(1)).setGsmBroadcastActivation(eq(false), msgCaptor.capture());
+
+        msg = msgCaptor.getValue();
+        assertNotNull(msg);
+        AsyncResult.forMessage(msg);
+        msg.sendToTarget();
+        processAllMessages();
+
+        verify(mSpyCi, times(2)).setCdmaBroadcastConfig(cdmaCaptor.capture(), msgCaptor.capture());
+        assertEquals(0, ((CdmaSmsBroadcastConfigInfo[]) cdmaCaptor.getValue()).length);
+
+        msg = msgCaptor.getValue();
+        assertNotNull(msg);
+        AsyncResult.forMessage(msg);
+        msg.sendToTarget();
+        processAllMessages();
+
+        // Verify to deavtivate cdma broadcast on empty ranges
+        verify(mSpyCi, times(1)).setCdmaBroadcastActivation(eq(false), msgCaptor.capture());
+
+        msg = msgCaptor.getValue();
+        assertNotNull(msg);
+        AsyncResult.forMessage(msg);
+        msg.sendToTarget();
+        processAllMessages();
+
+        assertTrue(mPhone.getCellBroadcastIdRanges().isEmpty());
+
+        //Verify to set gsm and cdma config then activate again
+        mPhone.setCellBroadcastIdRanges(ranges, r -> assertTrue(
+                TelephonyManager.CELL_BROADCAST_RESULT_SUCCESS == r));
+
+        processAllMessages();
+
+        verify(mSpyCi, times(3)).setGsmBroadcastConfig(gsmCaptor.capture(), msgCaptor.capture());
+        gsmArgs = Arrays.asList((SmsBroadcastConfigInfo[]) gsmCaptor.getValue());
+        assertEquals(gsmConfigs, gsmArgs);
+
+        msg = msgCaptor.getValue();
+        assertNotNull(msg);
+        AsyncResult.forMessage(msg);
+        msg.sendToTarget();
+        processAllMessages();
+
+        verify(mSpyCi, times(2)).setGsmBroadcastActivation(eq(true), msgCaptor.capture());
+
+        msg = msgCaptor.getValue();
+        assertNotNull(msg);
+        AsyncResult.forMessage(msg);
+        msg.sendToTarget();
+        processAllMessages();
+
+        verify(mSpyCi, times(3)).setCdmaBroadcastConfig(cdmaCaptor.capture(), msgCaptor.capture());
+        cdmaArgs = Arrays.asList((CdmaSmsBroadcastConfigInfo[]) cdmaCaptor.getValue());
+        assertEquals(cdmaConfigs, cdmaArgs);
+
+        msg = msgCaptor.getValue();
+        assertNotNull(msg);
+        AsyncResult.forMessage(msg);
+        msg.sendToTarget();
+        processAllMessages();
+
+        verify(mSpyCi, times(2)).setCdmaBroadcastActivation(eq(true), msgCaptor.capture());
+
+        msg = msgCaptor.getValue();
+        assertNotNull(msg);
+        AsyncResult.forMessage(msg);
+        msg.sendToTarget();
+        processAllMessages();
+
+        assertEquals(mPhone.getCellBroadcastIdRanges(), mergeRangesAsNeeded(ranges));
+    }
+
+    @Test
+    public void testSetCellBroadcastIdRangesFailure() throws Exception {
+        List<CellBroadcastIdRange> ranges = new ArrayList<>();
+
+        // Verify to throw exception for invalid ranges
+        ranges.add(new CellBroadcastIdRange(0, 999, SmsCbMessage.MESSAGE_FORMAT_3GPP, true));
+        ranges.add(new CellBroadcastIdRange(0, 999, SmsCbMessage.MESSAGE_FORMAT_3GPP, false));
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mPhone.setCellBroadcastIdRanges(ranges, r -> {}));
+
+        ArgumentCaptor<Message> msgCaptor = ArgumentCaptor.forClass(Message.class);
+        ranges.clear();
+        ranges.add(new CellBroadcastIdRange(0, 999, SmsCbMessage.MESSAGE_FORMAT_3GPP, true));
+        ranges.add(new CellBroadcastIdRange(0, 999, SmsCbMessage.MESSAGE_FORMAT_3GPP2, true));
+
+        mockCommandInterface();
+
+        // Verify the result on setGsmBroadcastConfig failure
+        mPhone.setCellBroadcastIdRanges(ranges, r -> assertTrue(
+                TelephonyManager.CELL_BROADCAST_RESULT_FAIL_CONFIG == r));
+        processAllMessages();
+
+        verify(mSpyCi, times(1)).setGsmBroadcastConfig(any(), msgCaptor.capture());
+
+        Message msg = msgCaptor.getValue();
+        assertNotNull(msg);
+        AsyncResult.forMessage(msg).exception = new RuntimeException();
+        msg.sendToTarget();
+        processAllMessages();
+
+        verify(mSpyCi, times(0)).setGsmBroadcastActivation(anyBoolean(), any());
+        verify(mSpyCi, times(0)).setCdmaBroadcastConfig(any(), any());
+        verify(mSpyCi, times(0)).setCdmaBroadcastActivation(anyBoolean(), any());
+        assertTrue(mPhone.getCellBroadcastIdRanges().isEmpty());
+
+        // Verify the result on setGsmBroadcastActivation failure
+        mPhone.setCellBroadcastIdRanges(ranges, r -> assertTrue(
+                TelephonyManager.CELL_BROADCAST_RESULT_FAIL_ACTIVATION == r));
+        processAllMessages();
+
+        verify(mSpyCi, times(2)).setGsmBroadcastConfig(any(), msgCaptor.capture());
+
+        msg = msgCaptor.getValue();
+        assertNotNull(msg);
+        AsyncResult.forMessage(msg);
+        msg.sendToTarget();
+        processAllMessages();
+
+        verify(mSpyCi, times(1)).setGsmBroadcastActivation(anyBoolean(), msgCaptor.capture());
+
+        msg = msgCaptor.getValue();
+        assertNotNull(msg);
+        AsyncResult.forMessage(msg).exception = new RuntimeException();
+        msg.sendToTarget();
+        processAllMessages();
+
+        verify(mSpyCi, times(0)).setCdmaBroadcastConfig(any(), any());
+        verify(mSpyCi, times(0)).setCdmaBroadcastActivation(anyBoolean(), any());
+        assertTrue(mPhone.getCellBroadcastIdRanges().isEmpty());
+
+        // Verify the result on setCdmaBroadcastConfig failure
+        mPhone.setCellBroadcastIdRanges(ranges, r -> assertTrue(
+                TelephonyManager.CELL_BROADCAST_RESULT_FAIL_CONFIG == r));
+        processAllMessages();
+
+        verify(mSpyCi, times(3)).setGsmBroadcastConfig(any(), msgCaptor.capture());
+
+        msg = msgCaptor.getValue();
+        assertNotNull(msg);
+        AsyncResult.forMessage(msg);
+        msg.sendToTarget();
+        processAllMessages();
+
+        verify(mSpyCi, times(2)).setGsmBroadcastActivation(anyBoolean(), msgCaptor.capture());
+
+        msg = msgCaptor.getValue();
+        assertNotNull(msg);
+        AsyncResult.forMessage(msg);
+        msg.sendToTarget();
+        processAllMessages();
+
+        verify(mSpyCi, times(1)).setCdmaBroadcastConfig(any(), msgCaptor.capture());
+
+        msg = msgCaptor.getValue();
+        assertNotNull(msg);
+        AsyncResult.forMessage(msg).exception = new RuntimeException();
+        msg.sendToTarget();
+        processAllMessages();
+
+        verify(mSpyCi, times(0)).setCdmaBroadcastActivation(anyBoolean(), any());
+
+        List<CellBroadcastIdRange> ranges3gpp = new ArrayList<>();
+        ranges3gpp.add(new CellBroadcastIdRange(0, 999, SmsCbMessage.MESSAGE_FORMAT_3GPP, true));
+
+        assertEquals(mPhone.getCellBroadcastIdRanges(), ranges3gpp);
+
+        // Verify the result on setCdmaBroadcastActivation failure
+        mPhone.setCellBroadcastIdRanges(ranges, r -> assertTrue(
+                TelephonyManager.CELL_BROADCAST_RESULT_FAIL_ACTIVATION == r));
+        processAllMessages();
+
+        // Verify no more calls as there is no change of ranges for 3gpp
+        verify(mSpyCi, times(3)).setGsmBroadcastConfig(any(), any());
+        verify(mSpyCi, times(2)).setGsmBroadcastActivation(anyBoolean(), any());
+        verify(mSpyCi, times(2)).setCdmaBroadcastConfig(any(), msgCaptor.capture());
+
+        msg = msgCaptor.getValue();
+        assertNotNull(msg);
+        AsyncResult.forMessage(msg);
+        msg.sendToTarget();
+        processAllMessages();
+
+        verify(mSpyCi, times(1)).setCdmaBroadcastActivation(anyBoolean(), msgCaptor.capture());
+
+        msg = msgCaptor.getValue();
+        assertNotNull(msg);
+        AsyncResult.forMessage(msg).exception = new RuntimeException();
+        msg.sendToTarget();
+        processAllMessages();
+
+        assertEquals(mPhone.getCellBroadcastIdRanges(), ranges3gpp);
+    }
+
+    @Test
+    public void testClearCellBroadcastConfigOnRadioOff() {
+        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);
+
+        mPhone.sendEmptyMessage(Phone.EVENT_RADIO_OFF_OR_NOT_AVAILABLE);
+        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));
+
+        mPhone.setCellBroadcastIdRanges(ranges, r -> assertTrue(
+                TelephonyManager.CELL_BROADCAST_RESULT_SUCCESS == r));
+        processAllMessages();
+
+        assertEquals(mPhone.getCellBroadcastIdRanges(), ranges);
+
+        mTracker.mSubChangedListener.onSubscriptionsChanged();
+        processAllMessages();
+
+        // Verify the config is not reset when the sub id is not changed
+        assertEquals(mPhone.getCellBroadcastIdRanges(), ranges);
+
+        mTracker.mSubId = mTracker.mSubId % SubscriptionManager.DEFAULT_SUBSCRIPTION_ID + 1;
+
+        mTracker.mSubChangedListener.onSubscriptionsChanged();
+        processAllMessages();
+
+        // Verify the config is reset when the sub id is changed
+        assertTrue(mPhone.getCellBroadcastIdRanges().isEmpty());
+    }
+
+    @Test
+    public void testMergeCellBroadcastIdRangesAsNeeded() {
+        final int[][] channelValues = {
+                {0, 999}, {1000, 1003}, {1004, 0x0FFF}, {0x1000, 0x10FF}, {0x1100, 0x112F},
+                {0x1130, 0x1900}, {0x1901, 0x9FFF}, {0xA000, 0xFFFE}, {0xFFFF, 0xFFFF}};
+        final int[] typeValues = {
+                SmsCbMessage.MESSAGE_FORMAT_3GPP, SmsCbMessage.MESSAGE_FORMAT_3GPP2};
+        final boolean[] enabledValues = {true, false};
+
+        List<CellBroadcastIdRange> ranges = new ArrayList<>();
+        for (int i = 0; i < channelValues.length; i++) {
+            ranges.add(new CellBroadcastIdRange(channelValues[i][0], channelValues[i][1],
+                    typeValues[0], enabledValues[0]));
+        }
+
+        ranges = mergeRangesAsNeeded(ranges);
+
+        assertEquals(1, ranges.size());
+        assertEquals(ranges.get(0).getStartId(), channelValues[0][0]);
+        assertEquals(ranges.get(0).getEndId(), channelValues[channelValues.length - 1][0]);
+
+        // Verify not to merge the ranges with different types.
+        ranges.clear();
+        for (int i = 0; i < channelValues.length; i++) {
+            ranges.add(new CellBroadcastIdRange(channelValues[i][0], channelValues[i][1],
+                    typeValues[0], enabledValues[0]));
+            ranges.add(new CellBroadcastIdRange(channelValues[i][0], channelValues[i][1],
+                    typeValues[1], enabledValues[0]));
+        }
+
+        ranges = mergeRangesAsNeeded(ranges);
+
+        assertEquals(2, ranges.size());
+        assertEquals(ranges.get(0).getStartId(), channelValues[0][0]);
+        assertEquals(ranges.get(0).getEndId(), channelValues[channelValues.length - 1][0]);
+        assertEquals(ranges.get(1).getStartId(), channelValues[0][0]);
+        assertEquals(ranges.get(1).getEndId(), channelValues[channelValues.length - 1][0]);
+        assertTrue(ranges.get(0).getType() != ranges.get(1).getType());
+
+        // Verify to throw IllegalArgumentException if the same range is enabled and disabled
+        // in the range list.
+        final List<CellBroadcastIdRange> ranges2 = new ArrayList<>();
+        for (int i = 0; i < channelValues.length; i++) {
+            ranges2.add(new CellBroadcastIdRange(channelValues[i][0], channelValues[i][1],
+                    typeValues[0], enabledValues[0]));
+            ranges2.add(new CellBroadcastIdRange(channelValues[i][0], channelValues[i][1],
+                    typeValues[0], enabledValues[1]));
+        }
+
+        assertThrows(IllegalArgumentException.class, () ->
+                mergeRangesAsNeeded(ranges2));
+    }
+
+    @Test
+    public void testMakeCellBroadcastConfigTracker() {
+        Phone phone = spy(mPhone);
+        CellBroadcastConfigTracker tracker = CellBroadcastConfigTracker.make(phone, phone, false);
+        processAllMessages();
+
+        verify(phone, never()).registerForRadioOffOrNotAvailable(any(), anyInt(), any());
+        verify(mSubscriptionManager, never()).addOnSubscriptionsChangedListener(
+                any(), eq(tracker.mSubChangedListener));
+
+        tracker.start();
+        processAllMessages();
+
+        verify(phone, times(1)).registerForRadioOffOrNotAvailable(any(), anyInt(), any());
+        verify(mSubscriptionManager, times(1)).addOnSubscriptionsChangedListener(
+                any(), eq(tracker.mSubChangedListener));
+
+        tracker = CellBroadcastConfigTracker.make(phone, phone, true);
+        processAllMessages();
+
+        verify(phone, times(2)).registerForRadioOffOrNotAvailable(any(), anyInt(), any());
+        verify(mSubscriptionManager, times(1)).addOnSubscriptionsChangedListener(
+                any(), eq(tracker.mSubChangedListener));
+    }
+
+    private void mockCommandInterface() {
+        doNothing().when(mSpyCi).setGsmBroadcastConfig(any(), any());
+        doNothing().when(mSpyCi).setGsmBroadcastActivation(anyBoolean(), any());
+        doNothing().when(mSpyCi).setCdmaBroadcastConfig(any(), any());
+        doNothing().when(mSpyCi).setCdmaBroadcastActivation(anyBoolean(), any());
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthNrTest.java b/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthNrTest.java
index 47f545f..11d57bf 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthNrTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CellSignalStrengthNrTest.java
@@ -22,7 +22,7 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 
-import android.hardware.radio.V1_6.NrSignalStrength;
+import android.hardware.radio.network.NrSignalStrength;
 import android.os.Parcel;
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
@@ -55,9 +55,11 @@
     private static final int CSICQI_TABLE_INDEX = 1;
     private static final ArrayList<Byte> CSICQI_REPORT =
             new ArrayList<>(Arrays.asList((byte) 3, (byte) 2, (byte) 1));
+    private static final byte[] CSICQI_REPORT_PRIMITIVE = new byte[] {(byte) 3, (byte) 2, (byte) 1};
     private static final int SSRSRP = -112;
     private static final int SSRSRQ = -13;
     private static final int SSSINR = 32;
+    private static final int TIMING_ADVANCE = 10;
 
     // Mocked classes
     ServiceState mSS;
@@ -83,7 +85,7 @@
     public void testGetMethod() {
         // GIVEN an instance of CellSignalStrengthNr
         CellSignalStrengthNr css = new CellSignalStrengthNr(CSIRSRP, CSIRSRQ, CSISINR,
-                CSICQI_TABLE_INDEX, CSICQI_REPORT, SSRSRP, SSRSRQ, SSSINR);
+                CSICQI_TABLE_INDEX, CSICQI_REPORT, SSRSRP, SSRSRQ, SSSINR, TIMING_ADVANCE);
 
         // THEN the get method should return correct value
         assertThat(css.getCsiRsrp()).isEqualTo(CSIRSRP);
@@ -95,20 +97,22 @@
         assertThat(css.getSsRsrq()).isEqualTo(SSRSRQ);
         assertThat(css.getSsSinr()).isEqualTo(SSSINR);
         assertThat(css.getDbm()).isEqualTo(SSRSRP);
+        assertThat(css.getTimingAdvanceMicros()).isEqualTo(TIMING_ADVANCE);
     }
 
     @Test
     public void testGetMethodWithHal() {
         // GIVEN an instance of NrSignalStrength with some positive values
         NrSignalStrength nrSignalStrength = new NrSignalStrength();
-        nrSignalStrength.base.csiRsrp = -CSIRSRP;
-        nrSignalStrength.base.csiRsrq = -CSIRSRQ;
-        nrSignalStrength.base.csiSinr = CSISINR;
+        nrSignalStrength.csiRsrp = -CSIRSRP;
+        nrSignalStrength.csiRsrq = -CSIRSRQ;
+        nrSignalStrength.csiSinr = CSISINR;
         nrSignalStrength.csiCqiTableIndex = CSICQI_TABLE_INDEX;
-        nrSignalStrength.csiCqiReport = CSICQI_REPORT;
-        nrSignalStrength.base.ssRsrp = -SSRSRP;
-        nrSignalStrength.base.ssRsrq = -SSRSRQ;
-        nrSignalStrength.base.ssSinr = SSSINR;
+        nrSignalStrength.csiCqiReport = CSICQI_REPORT_PRIMITIVE;
+        nrSignalStrength.ssRsrp = -SSRSRP;
+        nrSignalStrength.ssRsrq = -SSRSRQ;
+        nrSignalStrength.ssSinr = SSSINR;
+        nrSignalStrength.timingAdvance = TIMING_ADVANCE;
 
         // THEN the get method should return the correct value
         CellSignalStrengthNr css = RILUtils.convertHalNrSignalStrength(nrSignalStrength);
@@ -121,20 +125,22 @@
         assertThat(css.getSsRsrq()).isEqualTo(SSRSRQ);
         assertThat(css.getSsSinr()).isEqualTo(SSSINR);
         assertThat(css.getDbm()).isEqualTo(SSRSRP);
+        assertThat(css.getTimingAdvanceMicros()).isEqualTo(TIMING_ADVANCE);
     }
 
     @Test
     public void testUnavailableValueWithHal() {
         // GIVEN an instance of NrSignalStrength
         NrSignalStrength nrSignalStrength = new NrSignalStrength();
-        nrSignalStrength.base.csiRsrp = CellInfo.UNAVAILABLE;
-        nrSignalStrength.base.csiRsrq = CellInfo.UNAVAILABLE;
-        nrSignalStrength.base.csiSinr = CellInfo.UNAVAILABLE;
+        nrSignalStrength.csiRsrp = CellInfo.UNAVAILABLE;
+        nrSignalStrength.csiRsrq = CellInfo.UNAVAILABLE;
+        nrSignalStrength.csiSinr = CellInfo.UNAVAILABLE;
         nrSignalStrength.csiCqiTableIndex = CellInfo.UNAVAILABLE;
-        nrSignalStrength.csiCqiReport = new ArrayList<Byte>();
-        nrSignalStrength.base.ssRsrp = CellInfo.UNAVAILABLE;
-        nrSignalStrength.base.ssRsrq = CellInfo.UNAVAILABLE;
-        nrSignalStrength.base.ssSinr = CellInfo.UNAVAILABLE;
+        nrSignalStrength.csiCqiReport = new byte[]{};
+        nrSignalStrength.ssRsrp = CellInfo.UNAVAILABLE;
+        nrSignalStrength.ssRsrq = CellInfo.UNAVAILABLE;
+        nrSignalStrength.ssSinr = CellInfo.UNAVAILABLE;
+        nrSignalStrength.timingAdvance = CellInfo.UNAVAILABLE;
 
         // THEN the get method should return unavailable value
         CellSignalStrengthNr css = RILUtils.convertHalNrSignalStrength(nrSignalStrength);
@@ -147,15 +153,16 @@
         assertThat(css.getSsRsrq()).isEqualTo(CellInfo.UNAVAILABLE);
         assertThat(css.getSsSinr()).isEqualTo(CellInfo.UNAVAILABLE);
         assertThat(css.getDbm()).isEqualTo(CellInfo.UNAVAILABLE);
+        assertThat(css.getTimingAdvanceMicros()).isEqualTo(CellInfo.UNAVAILABLE);
     }
 
     @Test
     public void testEquals_sameParameters() {
         // GIVEN an instance of CellSignalStrengthNr and another object with the same parameters
         CellSignalStrengthNr css = new CellSignalStrengthNr(CSIRSRP, CSIRSRQ, CSISINR,
-                CSICQI_TABLE_INDEX, CSICQI_REPORT, SSRSRP, SSRSRQ, SSSINR);
+                CSICQI_TABLE_INDEX, CSICQI_REPORT, SSRSRP, SSRSRQ, SSSINR, TIMING_ADVANCE);
         CellSignalStrengthNr anotherCss = new CellSignalStrengthNr(CSIRSRP, CSIRSRQ, CSISINR,
-                CSICQI_TABLE_INDEX, CSICQI_REPORT, SSRSRP, SSRSRQ, SSSINR);
+                CSICQI_TABLE_INDEX, CSICQI_REPORT, SSRSRP, SSRSRQ, SSSINR, TIMING_ADVANCE);
 
         // THEN this two objects are equivalent
         assertThat(css).isEqualTo(anotherCss);
@@ -166,10 +173,10 @@
         // GIVEN an instance of CellSignalStrengthNr and another object with some different
         // parameters
         CellSignalStrengthNr css = new CellSignalStrengthNr(CSIRSRP, CSIRSRQ, CSISINR,
-                CSICQI_TABLE_INDEX, CSICQI_REPORT, SSRSRP, SSRSRQ, SSSINR);
+                CSICQI_TABLE_INDEX, CSICQI_REPORT, SSRSRP, SSRSRQ, SSSINR, TIMING_ADVANCE);
         CellSignalStrengthNr anotherCss = new CellSignalStrengthNr(ANOTHER_CSIRSRP,
                 ANOTHER_CSIRSRQ, CSISINR, CSICQI_TABLE_INDEX, CSICQI_REPORT,
-                SSRSRP, SSRSRQ, SSSINR);
+                SSRSRP, SSRSRQ, SSSINR, TIMING_ADVANCE);
 
         // THEN this two objects are different
         assertThat(css).isNotEqualTo(anotherCss);
@@ -179,7 +186,7 @@
     public void testAusLevel_validValue() {
         // GIVEN an instance of CellSignalStrengthNr with valid csirsrp
         CellSignalStrengthNr css = new CellSignalStrengthNr(CSIRSRP, CSIRSRQ, CSISINR,
-                CSICQI_TABLE_INDEX, CSICQI_REPORT, SSRSRP, SSRSRQ, SSSINR);
+                CSICQI_TABLE_INDEX, CSICQI_REPORT, SSRSRP, SSRSRQ, SSSINR, TIMING_ADVANCE);
 
         // THEN the asu level is in range [0, 97]
         assertThat(css.getAsuLevel()).isIn(Range.range(0, BoundType.CLOSED, 97, BoundType.CLOSED));
@@ -189,7 +196,7 @@
     public void testAsuLevel_invalidValue() {
         // GIVEN an instance of CellSignalStrengthNr with invalid csirsrp
         CellSignalStrengthNr css = new CellSignalStrengthNr(INVALID_CSIRSRP, CSIRSRQ, CSISINR,
-                CSICQI_TABLE_INDEX, CSICQI_REPORT, INVALID_SSRSRP, SSRSRQ, SSSINR);
+                CSICQI_TABLE_INDEX, CSICQI_REPORT, INVALID_SSRSRP, SSRSRQ, SSSINR, TIMING_ADVANCE);
 
         // THEN the asu level is unknown
         assertThat(css.getAsuLevel()).isEqualTo(CellSignalStrengthNr.UNKNOWN_ASU_LEVEL);
@@ -200,7 +207,7 @@
         for (int ssRsrp = -156; ssRsrp <= -31; ssRsrp++) {
             // GIVEN an instance of CellSignalStrengthNr with valid csirsrp
             CellSignalStrengthNr css = new CellSignalStrengthNr(CSIRSRP, CSIRSRQ, CSISINR,
-                    CSICQI_TABLE_INDEX, CSICQI_REPORT, ssRsrp, SSRSRQ, SSSINR);
+                    CSICQI_TABLE_INDEX, CSICQI_REPORT, ssRsrp, SSRSRQ, SSSINR, TIMING_ADVANCE);
 
             // THEN the signal level is valid
             assertThat(css.getLevel()).isIn(Range.range(
@@ -213,7 +220,7 @@
     public void testSignalLevel_invalidValue() {
         // GIVEN an instance of CellSignalStrengthNr with invalid csirsrp
         CellSignalStrengthNr css = new CellSignalStrengthNr(INVALID_CSIRSRP, CSIRSRQ, CSISINR,
-                CSICQI_TABLE_INDEX, CSICQI_REPORT, SSRSRP, SSRSRQ, SSSINR);
+                CSICQI_TABLE_INDEX, CSICQI_REPORT, SSRSRP, SSRSRQ, SSSINR, TIMING_ADVANCE);
 
         // THEN the signal level is unknown
         assertThat(css.getLevel()).isEqualTo(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
@@ -223,7 +230,7 @@
     public void testParcel() {
         // GIVEN an instance of CellSignalStrengthNr
         CellSignalStrengthNr css = new CellSignalStrengthNr(CSIRSRP, CSIRSRQ, CSISINR,
-                CSICQI_TABLE_INDEX, CSICQI_REPORT, SSRSRP, SSRSRQ, SSSINR);
+                CSICQI_TABLE_INDEX, CSICQI_REPORT, SSRSRP, SSRSRQ, SSSINR, TIMING_ADVANCE);
 
         // WHEN write the object to parcel and create another object with that parcel
         Parcel parcel = Parcel.obtain();
@@ -241,6 +248,7 @@
         assertThat(anotherCss.getSsRsrp()).isEqualTo(SSRSRP);
         assertThat(anotherCss.getSsRsrq()).isEqualTo(SSRSRQ);
         assertThat(anotherCss.getSsSinr()).isEqualTo(SSSINR);
+        assertThat(anotherCss.getTimingAdvanceMicros()).isEqualTo(TIMING_ADVANCE);
     }
 
     @Test
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/ConnectionTest.java
old mode 100755
new mode 100644
index d1e643e..0bce5cb
--- a/tests/telephonytests/src/com/android/internal/telephony/ConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ConnectionTest.java
@@ -20,12 +20,16 @@
 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.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import android.os.Handler;
 import android.os.Looper;
 
+import com.android.internal.telephony.emergency.EmergencyNumberTracker;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -39,6 +43,9 @@
 
     // Mocked classes
     protected Call mCall;
+    protected EmergencyNumberTracker mEmergencyNumberTracker2;
+    protected Connection.PhoneFactoryProxy mPhoneFactoryProxy;
+    protected Connection mTestConnection;
 
     private class TestConnection extends Connection {
 
@@ -117,7 +124,15 @@
         super.setUp(getClass().getSimpleName());
         mCall = mock(Call.class);
         doReturn(mPhone).when(mCall).getPhone();
+        doReturn(mPhone).when(mCT).getPhone();
         replaceInstance(Handler.class, "mLooper", mCT, Looper.getMainLooper());
+
+        mEmergencyNumberTracker2 = mock(EmergencyNumberTracker.class);
+        doReturn(mEmergencyNumberTracker2).when(mPhone2).getEmergencyNumberTracker();
+
+        mTestConnection = new TestConnection(TEST_PHONE_TYPE);
+        mPhoneFactoryProxy = mock(Connection.PhoneFactoryProxy.class);
+        mTestConnection.setPhoneFactoryProxy(mPhoneFactoryProxy);
     }
 
     @After
@@ -156,5 +171,24 @@
         assertTrue(connection.hasKnownUserIntentEmergency());
     }
 
+    @Test
+    public void testSetEmergencyCallInfo() {
+        //Replicate Dual-SIM:
+        Phone [] phones = {mPhone, mPhone2};
+        when(mPhoneFactoryProxy.getPhones()).thenReturn(phones);
+        doReturn(1).when(mPhone).getPhoneId();
+        doReturn(2).when(mPhone2).getPhoneId();
+
+        //Replicate behavior when a number is an emergency number
+        // on the secondary SIM but not on the default SIM:
+        when(mPhone.getEmergencyNumberTracker().getEmergencyNumber(any())).thenReturn(null);
+        when(mEmergencyNumberTracker2.getEmergencyNumber(any()))
+                .thenReturn(getTestEmergencyNumber());
+
+        //Ensure the connection is considered as an emergency call:
+        mTestConnection.setEmergencyCallInfo(mCT);
+        assertTrue(mTestConnection.isEmergencyCall());
+    }
+
     // TODO Verify more methods in Connection
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
index 82ade73..ea19b62 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
@@ -176,6 +176,11 @@
                     mKeyValuePairs.put(request, (String)args.get("value"));
                     mNumKeyValuePairs++;
                     break;
+                case Settings.CALL_METHOD_PUT_CONFIG:
+                    logd("PUT_config called");
+                    logd("adding config flag: " + request + "-" + args.getString("value"));
+                    mFlags.put(request, args.getString("value"));
+                    break;
                 case Settings.CALL_METHOD_LIST_CONFIG:
                     logd("LIST_config: " + mFlags);
                     Bundle result = new Bundle();
@@ -351,6 +356,8 @@
                 return Context.POWER_SERVICE;
             } else if (serviceClass == EuiccManager.class) {
                 return Context.EUICC_SERVICE;
+            } else if (serviceClass == AlarmManager.class) {
+                return Context.ALARM_SERVICE;
             }
             return super.getSystemServiceName(serviceClass);
         }
@@ -773,6 +780,7 @@
 
         doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
         doReturn(mBundle).when(mCarrierConfigManager).getConfig();
+        doReturn(mBundle).when(mCarrierConfigManager).getConfigForSubId(anyInt(), anyString());
         doAnswer(invocation -> mNetworkId++).when(mNetwork).getNetId();
         doReturn(mNetwork).when(mConnectivityManager).registerNetworkAgent(
                 any(), any(), any(), any(), any(), any(), anyInt());
diff --git a/tests/telephonytests/src/com/android/internal/telephony/DataSpecificRegistrationInfoTest.java b/tests/telephonytests/src/com/android/internal/telephony/DataSpecificRegistrationInfoTest.java
new file mode 100644
index 0000000..50390cc
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/DataSpecificRegistrationInfoTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2022 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 junit.framework.Assert.assertNull;
+
+import android.os.Parcel;
+import android.telephony.DataSpecificRegistrationInfo;
+import android.telephony.LteVopsSupportInfo;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/** Unit tests for {@link DataSpecificRegistrationInfo}. */
+public class DataSpecificRegistrationInfoTest {
+    @Test
+    @SmallTest
+    public void testBuilder() {
+        DataSpecificRegistrationInfo dsri =
+                new DataSpecificRegistrationInfo.Builder(3)
+                .setNrAvailable(true)
+                .setEnDcAvailable(true)
+                .build();
+
+        assertEquals(3, dsri.maxDataCalls);
+        assertEquals(false, dsri.isDcNrRestricted);
+        assertEquals(true, dsri.isNrAvailable);
+        assertEquals(true, dsri.isEnDcAvailable);
+        assertNull(dsri.getVopsSupportInfo());
+
+        LteVopsSupportInfo vopsInfo = new LteVopsSupportInfo(
+                LteVopsSupportInfo.LTE_STATUS_SUPPORTED, LteVopsSupportInfo.LTE_STATUS_SUPPORTED);
+        dsri = new DataSpecificRegistrationInfo.Builder(5)
+                .setDcNrRestricted(true)
+                .setVopsSupportInfo(vopsInfo)
+                .build();
+
+        assertEquals(5, dsri.maxDataCalls);
+        assertEquals(true, dsri.isDcNrRestricted);
+        assertEquals(false, dsri.isNrAvailable);
+        assertEquals(false, dsri.isEnDcAvailable);
+        assertEquals(vopsInfo, dsri.getVopsSupportInfo());
+    }
+
+    @Test
+    @SmallTest
+    public void testParcel() {
+        DataSpecificRegistrationInfo dsri =
+                new DataSpecificRegistrationInfo.Builder(3)
+                .setNrAvailable(true)
+                .setEnDcAvailable(true)
+                .setLteAttachResultType(DataSpecificRegistrationInfo.LTE_ATTACH_TYPE_COMBINED)
+                .setLteAttachExtraInfo(DataSpecificRegistrationInfo.LTE_ATTACH_EXTRA_INFO_SMS_ONLY)
+                .build();
+
+        Parcel p = Parcel.obtain();
+        dsri.writeToParcel(p, 0);
+        p.setDataPosition(0);
+
+        DataSpecificRegistrationInfo newDsri =
+                DataSpecificRegistrationInfo.CREATOR.createFromParcel(p);
+        assertEquals(dsri, newDsri);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java b/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
index 6a05d4f..2f4182a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
@@ -16,6 +16,7 @@
 package com.android.internal.telephony;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.doReturn;
@@ -30,8 +31,11 @@
 import android.telephony.PreciseDisconnectCause;
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyManager;
+import android.telephony.ims.ImsCallProfile;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.internal.telephony.imsphone.ImsPhoneCall;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -53,6 +57,9 @@
     GsmCdmaCall mForeGroundCall;
     GsmCdmaCall mBackGroundCall;
     GsmCdmaCall mRingingCall;
+    ImsPhoneCall mImsForeGroundCall;
+    ImsPhoneCall mImsBackGroundCall;
+    ImsPhoneCall mImsRingingCall;
 
     @Before
     public void setUp() throws Exception {
@@ -62,6 +69,9 @@
         mForeGroundCall = mock(GsmCdmaCall.class);
         mBackGroundCall = mock(GsmCdmaCall.class);
         mRingingCall = mock(GsmCdmaCall.class);
+        mImsForeGroundCall = mock(ImsPhoneCall.class);
+        mImsBackGroundCall = mock(ImsPhoneCall.class);
+        mImsRingingCall = mock(ImsPhoneCall.class);
         mDefaultPhoneNotifierUT = new DefaultPhoneNotifier(mContext);
     }
 
@@ -168,61 +178,144 @@
 
     @Test @SmallTest
     public void testNotifyPreciseCallState() throws Exception {
-
         //mock forground/background/ringing call and call state
         doReturn(Call.State.IDLE).when(mForeGroundCall).getState();
         doReturn(Call.State.IDLE).when(mBackGroundCall).getState();
         doReturn(Call.State.IDLE).when(mRingingCall).getState();
 
-        mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone);
+        mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone, null, null, null);
         verify(mTelephonyRegistryManager, times(0)).notifyPreciseCallState(
-                anyInt(), anyInt(), anyInt(), anyInt(), anyInt());
+                anyInt(), anyInt(), any(), any(), any(), any());
 
         doReturn(mForeGroundCall).when(mPhone).getForegroundCall();
-        mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone);
+        mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone, null, null, null);
         verify(mTelephonyRegistryManager, times(0)).notifyPreciseCallState(
-                anyInt(), anyInt(), anyInt(), anyInt(), anyInt());
+                anyInt(), anyInt(), any(), any(), any(), any());
 
         doReturn(mBackGroundCall).when(mPhone).getBackgroundCall();
-        mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone);
+        mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone, null, null, null);
         verify(mTelephonyRegistryManager, times(0)).notifyPreciseCallState(
-                anyInt(), anyInt(), anyInt(), anyInt(), anyInt());
+                anyInt(), anyInt(), any(), any(), any(), any());
 
         doReturn(mRingingCall).when(mPhone).getRingingCall();
-        mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone);
-        verify(mTelephonyRegistryManager, times(1)).notifyPreciseCallState(
-                mPhone.getPhoneId(),
-                mPhone.getSubId(),
-                PreciseCallState.PRECISE_CALL_STATE_IDLE,
-                PreciseCallState.PRECISE_CALL_STATE_IDLE,
-                PreciseCallState.PRECISE_CALL_STATE_IDLE);
+        mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone, null, null, null);
+        ArgumentCaptor<int[]> captor = ArgumentCaptor.forClass(int[].class);
+        int phoneId = mPhone.getPhoneId();
+        int subId = mPhone.getSubId();
+        verify(mTelephonyRegistryManager).notifyPreciseCallState(
+                eq(phoneId), eq(subId), captor.capture(), eq(null), eq(null), eq(null));
+        final int[] callStates = captor.getValue();
+        assertEquals(3, callStates.length);
+        assertEquals(PreciseCallState.PRECISE_CALL_STATE_IDLE,
+                callStates[/*ringing call*/ 0]);
+        assertEquals(PreciseCallState.PRECISE_CALL_STATE_IDLE,
+                callStates[/*foreground call*/ 1]);
+        assertEquals(PreciseCallState.PRECISE_CALL_STATE_IDLE,
+                callStates[/*background call*/ 2]);
 
         doReturn(Call.State.ACTIVE).when(mForeGroundCall).getState();
-        mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone);
-        verify(mTelephonyRegistryManager, times(1)).notifyPreciseCallState(
-                mPhone.getPhoneId(),
-                mPhone.getSubId(),
-                PreciseCallState.PRECISE_CALL_STATE_IDLE,
-                PreciseCallState.PRECISE_CALL_STATE_ACTIVE,
-                PreciseCallState.PRECISE_CALL_STATE_IDLE);
+        mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone, null, null, null);
+        ArgumentCaptor<int[]> captor1 = ArgumentCaptor.forClass(int[].class);
+        phoneId = mPhone.getPhoneId();
+        subId = mPhone.getSubId();
+        verify(mTelephonyRegistryManager, times(2)).notifyPreciseCallState(
+                eq(phoneId), eq(subId), captor1.capture(), eq(null), eq(null), eq(null));
+        final int[] callStates1 = captor1.getValue();
+        assertEquals(3, callStates1.length);
+        assertEquals(PreciseCallState.PRECISE_CALL_STATE_IDLE,
+                callStates1[/*ringing call*/ 0]);
+        assertEquals(PreciseCallState.PRECISE_CALL_STATE_ACTIVE,
+                callStates1[/*foreground call*/ 1]);
+        assertEquals(PreciseCallState.PRECISE_CALL_STATE_IDLE,
+                callStates1[/*background call*/ 2]);
 
         doReturn(Call.State.HOLDING).when(mBackGroundCall).getState();
-        mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone);
-        verify(mTelephonyRegistryManager, times(1)).notifyPreciseCallState(
-                mPhone.getPhoneId(),
-                mPhone.getSubId(),
-                PreciseCallState.PRECISE_CALL_STATE_IDLE,
-                PreciseCallState.PRECISE_CALL_STATE_ACTIVE,
-                PreciseCallState.PRECISE_CALL_STATE_HOLDING);
+        mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone, null, null, null);
+        ArgumentCaptor<int[]> captor2 = ArgumentCaptor.forClass(int[].class);
+        verify(mTelephonyRegistryManager, times(3)).notifyPreciseCallState(
+                eq(phoneId), eq(subId), captor2.capture(), eq(null), eq(null), eq(null));
+        final int[] callStates2 = captor2.getValue();
+        assertEquals(3, callStates2.length);
+        assertEquals(PreciseCallState.PRECISE_CALL_STATE_IDLE,
+                callStates2[/*ringing call*/ 0]);
+        assertEquals(PreciseCallState.PRECISE_CALL_STATE_ACTIVE,
+                callStates2[/*foreground call*/ 1]);
+        assertEquals(PreciseCallState.PRECISE_CALL_STATE_HOLDING,
+                callStates2[/*background call*/ 2]);
 
         doReturn(Call.State.ALERTING).when(mRingingCall).getState();
-        mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone);
+        mDefaultPhoneNotifierUT.notifyPreciseCallState(mPhone, null, null, null);
+        ArgumentCaptor<int[]> captor3 = ArgumentCaptor.forClass(int[].class);
+        verify(mTelephonyRegistryManager, times(4)).notifyPreciseCallState(
+                eq(phoneId), eq(subId), captor3.capture(), eq(null), eq(null), eq(null));
+        final int[] callStates3 = captor3.getValue();
+        assertEquals(3, callStates3.length);
+        assertEquals(PreciseCallState.PRECISE_CALL_STATE_ALERTING,
+                callStates3[/*ringing call*/ 0]);
+        assertEquals(PreciseCallState.PRECISE_CALL_STATE_ACTIVE,
+                callStates3[/*foreground call*/ 1]);
+        assertEquals(PreciseCallState.PRECISE_CALL_STATE_HOLDING,
+                callStates3[/*background call*/ 2]);
+    }
+
+    @Test
+    public void testNotifyPreciseCallStateImsCallInfo() throws Exception {
+        //mock forground/background/ringing call and call state
+        doReturn(Call.State.ACTIVE).when(mImsForeGroundCall).getState();
+        doReturn(Call.State.HOLDING).when(mImsBackGroundCall).getState();
+        doReturn(Call.State.IDLE).when(mImsRingingCall).getState();
+
+        doReturn(mImsForeGroundCall).when(mImsPhone).getForegroundCall();
+        doReturn(mImsBackGroundCall).when(mImsPhone).getBackgroundCall();
+        doReturn(mImsRingingCall).when(mImsPhone).getRingingCall();
+
+        String[] imsCallIds = {null, "1", "2"};
+        int[] imsCallServiceTypes = {ImsCallProfile.SERVICE_TYPE_NONE,
+                ImsCallProfile.SERVICE_TYPE_NORMAL, ImsCallProfile.SERVICE_TYPE_NORMAL};
+        int[] imsCallTypes = {ImsCallProfile.CALL_TYPE_NONE,
+                ImsCallProfile.CALL_TYPE_VOICE, ImsCallProfile.CALL_TYPE_VT};
+
+        mDefaultPhoneNotifierUT
+                .notifyPreciseCallState(mImsPhone, imsCallIds, imsCallServiceTypes, imsCallTypes);
+        ArgumentCaptor<int[]> callStateCaptor = ArgumentCaptor.forClass(int[].class);
+        ArgumentCaptor<String[]> callIdCaptor = ArgumentCaptor.forClass(String[].class);
+        ArgumentCaptor<int[]> callServiceTypeCaptor = ArgumentCaptor.forClass(int[].class);
+        ArgumentCaptor<int[]> callTypeCaptor = ArgumentCaptor.forClass(int[].class);
+        int phoneId = mImsPhone.getPhoneId();
+        int subId = mImsPhone.getSubId();
         verify(mTelephonyRegistryManager, times(1)).notifyPreciseCallState(
-                mPhone.getPhoneId(),
-                mPhone.getSubId(),
-                PreciseCallState.PRECISE_CALL_STATE_ALERTING,
-                PreciseCallState.PRECISE_CALL_STATE_ACTIVE,
-                PreciseCallState.PRECISE_CALL_STATE_HOLDING);
+                eq(phoneId), eq(subId), callStateCaptor.capture(), callIdCaptor.capture(),
+                callServiceTypeCaptor.capture(), callTypeCaptor.capture());
+        final int[] callStates = callStateCaptor.getValue();
+        final String[] callIds = callIdCaptor.getValue();
+        final int[] callServiceTypes = callServiceTypeCaptor.getValue();
+        final int[] callTypes = callTypeCaptor.getValue();
+        assertEquals(3, callStates.length);
+        assertEquals(3, callIds.length);
+        assertEquals(3, callServiceTypes.length);
+        assertEquals(3, callServiceTypes.length);
+        assertEquals(PreciseCallState.PRECISE_CALL_STATE_IDLE,
+                callStates[/*ringing call*/ 0]);
+        assertEquals(PreciseCallState.PRECISE_CALL_STATE_ACTIVE,
+                callStates[/*foreground call*/ 1]);
+        assertEquals(PreciseCallState.PRECISE_CALL_STATE_HOLDING,
+                callStates[/*background call*/ 2]);
+
+        assertEquals("1", callIds[/*foreground call*/ 1]);
+        assertEquals("2", callIds[/*background call*/ 2]);
+        assertEquals(null, callIds[/*ringing call*/ 0]);
+        assertEquals(ImsCallProfile.SERVICE_TYPE_NORMAL,
+                callServiceTypes[/*foreground call*/ 1]);
+        assertEquals(ImsCallProfile.SERVICE_TYPE_NORMAL,
+                callServiceTypes[/*background call*/ 2]);
+        assertEquals(ImsCallProfile.SERVICE_TYPE_NONE,
+                callServiceTypes[/*ringing call*/ 0]);
+        assertEquals(ImsCallProfile.CALL_TYPE_VOICE,
+                callTypes[/*foreground call*/ 1]);
+        assertEquals(ImsCallProfile.CALL_TYPE_VT,
+                callTypes[/*background call*/ 2]);
+        assertEquals(ImsCallProfile.SERVICE_TYPE_NONE,
+                callServiceTypes[/*ringing call*/ 0]);
     }
 
     @Test @SmallTest
diff --git a/tests/telephonytests/src/com/android/internal/telephony/DisplayInfoControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/DisplayInfoControllerTest.java
index d2d8d96..f729b80 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/DisplayInfoControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/DisplayInfoControllerTest.java
@@ -102,7 +102,6 @@
         super.setUp(getClass().getSimpleName());
 
         doReturn((Executor) Runnable::run).when(mContext).getMainExecutor();
-
         mBundle = mContextFixture.getCarrierConfigBundle();
         mSstHandler = new ServiceStateTrackerTestHandler(getClass().getSimpleName());
         mSstHandler.start();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTrackerTest.java
index f0c43be..2fdff9e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaCallTrackerTest.java
@@ -41,9 +41,7 @@
 import androidx.test.filters.FlakyTest;
 
 import com.android.internal.telephony.PhoneInternalInterface.DialArgs;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
+import com.android.internal.telephony.domainselection.DomainSelectionResolver;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -53,6 +51,9 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class GsmCdmaCallTrackerTest extends TelephonyTest {
@@ -66,12 +67,16 @@
     // Mocked classes
     private GsmCdmaConnection mConnection;
     private Handler mHandler;
+    private DomainSelectionResolver mDomainSelectionResolver;
 
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
         mConnection = mock(GsmCdmaConnection.class);
         mHandler = mock(Handler.class);
+        mDomainSelectionResolver = mock(DomainSelectionResolver.class);
+        doReturn(false).when(mDomainSelectionResolver).isDomainSelectionSupported();
+        DomainSelectionResolver.setDomainSelectionResolver(mDomainSelectionResolver);
         mSimulatedCommands.setRadioPower(true, null);
         mPhone.mCi = this.mSimulatedCommands;
 
@@ -86,6 +91,7 @@
     @After
     public void tearDown() throws Exception {
         mCTUT = null;
+        DomainSelectionResolver.setDomainSelectionResolver(null);
         super.tearDown();
     }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
index b62d5f9..c5f20e3 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
@@ -19,9 +19,14 @@
 import static com.android.internal.telephony.CommandsInterface.CF_ACTION_ENABLE;
 import static com.android.internal.telephony.CommandsInterface.CF_REASON_UNCONDITIONAL;
 import static com.android.internal.telephony.Phone.EVENT_ICC_CHANGED;
+import static com.android.internal.telephony.Phone.EVENT_IMS_DEREGISTRATION_TRIGGERED;
+import static com.android.internal.telephony.Phone.EVENT_RADIO_AVAILABLE;
+import static com.android.internal.telephony.Phone.EVENT_SET_NULL_CIPHER_AND_INTEGRITY_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;
+import static com.android.internal.telephony.test.SimulatedCommands.FAKE_IMEI;
+import static com.android.internal.telephony.test.SimulatedCommands.FAKE_IMEISV;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -49,13 +54,18 @@
 
 import android.content.Intent;
 import android.content.SharedPreferences;
+import android.hardware.radio.modem.ImeiInfo;
 import android.os.AsyncResult;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
 import android.os.PersistableBundle;
 import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.WorkSource;
 import android.preference.PreferenceManager;
+import android.provider.DeviceConfig;
 import android.telecom.VideoProfile;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.CarrierConfigManager;
@@ -64,17 +74,24 @@
 import android.telephony.CellIdentityGsm;
 import android.telephony.LinkCapacityEstimate;
 import android.telephony.NetworkRegistrationInfo;
+import android.telephony.RadioAccessFamily;
 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 com.android.internal.telephony.domainselection.DomainSelectionResolver;
+import com.android.internal.telephony.emergency.EmergencyStateTracker;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.test.SimulatedCommands;
 import com.android.internal.telephony.test.SimulatedCommandsVerifier;
 import com.android.internal.telephony.uicc.AdnRecord;
@@ -104,6 +121,7 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class GsmCdmaPhoneTest extends TelephonyTest {
+    private static final String LOG_TAG = "GsmCdmaPhoneTest";
     private static final String TEST_EMERGENCY_NUMBER = "555";
 
     // Mocked classes
@@ -111,10 +129,16 @@
     private UiccSlot mUiccSlot;
     private CommandsInterface mMockCi;
     private AdnRecordCache adnRecordCache;
+    private DomainSelectionResolver mDomainSelectionResolver;
 
     //mPhoneUnderTest
     private GsmCdmaPhone mPhoneUT;
 
+    // Ideally we would use TestableDeviceConfig, but that's not doable because the Settings
+    // app is not currently debuggable. For now, we use the real device config and ensure that
+    // we reset the cellular_security namespace property to its pre-test value after every test.
+    private DeviceConfig.Properties mPreTestProperties;
+
     private static final int EVENT_EMERGENCY_CALLBACK_MODE_EXIT = 1;
     private static final int EVENT_EMERGENCY_CALL_TOGGLE = 2;
     private static final int EVENT_SET_ICC_LOCK_ENABLED = 3;
@@ -138,13 +162,18 @@
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
+        mPreTestProperties = DeviceConfig.getProperties(
+                TelephonyManager.PROPERTY_ENABLE_NULL_CIPHER_TOGGLE);
         mTestHandler = Mockito.mock(Handler.class);
         mUiccSlot = Mockito.mock(UiccSlot.class);
         mUiccPort = Mockito.mock(UiccPort.class);
         mMockCi = Mockito.mock(CommandsInterface.class);
         adnRecordCache = Mockito.mock(AdnRecordCache.class);
+        mDomainSelectionResolver = Mockito.mock(DomainSelectionResolver.class);
         doReturn(false).when(mSST).isDeviceShuttingDown();
         doReturn(true).when(mImsManager).isVolteEnabledByPlatform();
+        doReturn(false).when(mDomainSelectionResolver).isDomainSelectionSupported();
+        DomainSelectionResolver.setDomainSelectionResolver(mDomainSelectionResolver);
 
         mPhoneUT = new GsmCdmaPhone(mContext, mSimulatedCommands, mNotifier, true, 0,
             PhoneConstants.PHONE_TYPE_GSM, mTelephonyComponentFactory, (c, p) -> mImsManager);
@@ -162,6 +191,14 @@
     public void tearDown() throws Exception {
         mPhoneUT.removeCallbacksAndMessages(null);
         mPhoneUT = null;
+        DomainSelectionResolver.setDomainSelectionResolver(null);
+        try {
+            DeviceConfig.setProperties(mPreTestProperties);
+        } catch (DeviceConfig.BadConfigException e) {
+            Log.e(LOG_TAG,
+                    "Failed to reset DeviceConfig to pre-test state. Test results may be impacted. "
+                            + e.getMessage());
+        }
         super.tearDown();
     }
 
@@ -958,7 +995,7 @@
         verify(mTelephonyManager).setBasebandVersionForPhone(eq(mPhoneUT.getPhoneId()),
                 nullable(String.class));
         // IMEI
-        assertEquals(SimulatedCommands.FAKE_IMEI, mPhoneUT.getImei());
+        assertEquals(FAKE_IMEI, mPhoneUT.getImei());
         // IMEISV
         assertEquals(SimulatedCommands.FAKE_IMEISV, mPhoneUT.getDeviceSvn());
         // radio capability
@@ -984,7 +1021,7 @@
         verify(mTelephonyManager, times(2)).setBasebandVersionForPhone(eq(mPhoneUT.getPhoneId()),
                 nullable(String.class));
         // device identity
-        assertEquals(SimulatedCommands.FAKE_IMEI, mPhoneUT.getImei());
+        assertEquals(FAKE_IMEI, mPhoneUT.getImei());
         assertEquals(SimulatedCommands.FAKE_IMEISV, mPhoneUT.getDeviceSvn());
         assertEquals(SimulatedCommands.FAKE_ESN, mPhoneUT.getEsn());
         assertEquals(SimulatedCommands.FAKE_MEID, mPhoneUT.getMeid());
@@ -1055,8 +1092,6 @@
                 getVoiceCallForwardingFlag();
 
         // invalid subId
-        doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID).when(mSubscriptionController).
-                getSubId(anyInt());
         doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID).when(mSubscriptionManagerService)
                 .getSubId(anyInt());
         assertEquals(false, mPhoneUT.getCallForwardingIndicator());
@@ -1064,7 +1099,6 @@
         // valid subId, sharedPreference not present
         int subId1 = 0;
         int subId2 = 1;
-        doReturn(subId1).when(mSubscriptionController).getSubId(anyInt());
         doReturn(subId1).when(mSubscriptionManagerService).getSubId(anyInt());
         assertEquals(false, mPhoneUT.getCallForwardingIndicator());
 
@@ -1087,7 +1121,6 @@
         assertEquals(true, mPhoneUT.getCallForwardingIndicator());
 
         // check for another subId
-        doReturn(subId2).when(mSubscriptionController).getSubId(anyInt());
         doReturn(subId2).when(mSubscriptionManagerService).getSubId(anyInt());
         assertEquals(false, mPhoneUT.getCallForwardingIndicator());
 
@@ -1097,7 +1130,6 @@
         assertEquals(true, mPhoneUT.getCallForwardingIndicator());
 
         // switching back to previous subId, stored value should still be available
-        doReturn(subId1).when(mSubscriptionController).getSubId(anyInt());
         doReturn(subId1).when(mSubscriptionManagerService).getSubId(anyInt());
         assertEquals(true, mPhoneUT.getCallForwardingIndicator());
 
@@ -1260,7 +1292,6 @@
         processAllMessages();
 
         verify(mSubscriptionManagerService, never()).getAllSubInfoList(anyString(), anyString());
-        verify(mSubscriptionController, never()).getSubInfoForIccId(any());
 
         // Have IccId defined. But expected value and current value are the same. So no RIL command
         // should be sent.
@@ -1268,12 +1299,8 @@
         doReturn(iccId).when(mUiccSlot).getIccId(anyInt());
         Message.obtain(mPhoneUT, EVENT_ICC_CHANGED, null).sendToTarget();
         processAllMessages();
-        if (isSubscriptionManagerServiceEnabled()) {
-            verify(mSubscriptionManagerService).getAllSubInfoList(anyString(),
-                    nullable(String.class));
-        } else {
-            verify(mSubscriptionController).getSubInfoForIccId(iccId);
-        }
+        verify(mSubscriptionManagerService).getAllSubInfoList(anyString(),
+                nullable(String.class));
         verify(mMockCi, never()).enableUiccApplications(anyBoolean(), any());
     }
 
@@ -1309,11 +1336,12 @@
     public void testSetRadioPower() throws Exception {
         mPhoneUT.setRadioPower(false);
         verify(mSST).setRadioPowerForReason(false, false, false, false,
-                Phone.RADIO_POWER_REASON_USER);
+                TelephonyManager.RADIO_POWER_REASON_USER);
 
         // Turn on radio for emergency call.
         mPhoneUT.setRadioPower(true, true, false, true);
-        verify(mSST).setRadioPowerForReason(true, true, false, true, Phone.RADIO_POWER_REASON_USER);
+        verify(mSST).setRadioPowerForReason(true, true, false, true,
+                TelephonyManager.RADIO_POWER_REASON_USER);
     }
 
     @Test
@@ -1321,12 +1349,12 @@
     public void testSetRadioPowerOnForTestEmergencyCall() {
         mPhoneUT.setRadioPower(false);
         verify(mSST).setRadioPowerForReason(false, false, false, false,
-                Phone.RADIO_POWER_REASON_USER);
+                TelephonyManager.RADIO_POWER_REASON_USER);
 
         mPhoneUT.setRadioPowerOnForTestEmergencyCall(false);
         verify(mSST).clearAllRadioOffReasons();
         verify(mSST).setRadioPowerForReason(eq(true), eq(false), anyBoolean(), eq(false),
-                eq(Phone.RADIO_POWER_REASON_USER));
+                eq(TelephonyManager.RADIO_POWER_REASON_USER));
     }
 
     @Test
@@ -1442,8 +1470,6 @@
 
     @Test
     public void testEventCarrierConfigChanged() {
-        doReturn(null).when(mSubscriptionController).getSubscriptionProperty(anyInt(),
-                eq(SubscriptionManager.NR_ADVANCED_CALLING_ENABLED));
         doReturn(null).when(mSubscriptionManagerService).getSubscriptionProperty(anyInt(),
                 eq(SubscriptionManager.NR_ADVANCED_CALLING_ENABLED), anyString(), anyString());
 
@@ -1514,11 +1540,8 @@
     @SmallTest
     public void testLoadAllowedNetworksFromSubscriptionDatabase_loadTheNullValue_isLoadedTrue() {
         int subId = 1;
-        doReturn(subId).when(mSubscriptionController).getSubId(anyInt());
         doReturn(subId).when(mSubscriptionManagerService).getSubId(anyInt());
 
-        doReturn(null).when(mSubscriptionController).getSubscriptionProperty(anyInt(),
-                eq(SubscriptionManager.ALLOWED_NETWORK_TYPES));
         doReturn(null).when(mSubscriptionManagerService).getSubscriptionProperty(anyInt(),
                 eq(SubscriptionManager.ALLOWED_NETWORK_TYPES), anyString(), anyString());
 
@@ -1531,11 +1554,8 @@
     @SmallTest
     public void testLoadAllowedNetworksFromSubscriptionDatabase_subIdNotValid_isLoadedFalse() {
         int subId = -1;
-        doReturn(subId).when(mSubscriptionController).getSubId(anyInt());
         doReturn(subId).when(mSubscriptionManagerService).getSubId(anyInt());
 
-        when(mSubscriptionController.getSubscriptionProperty(anyInt(),
-                eq(SubscriptionManager.ALLOWED_NETWORK_TYPES))).thenReturn(null);
         when(mSubscriptionManagerService.getSubscriptionProperty(anyInt(),
                 eq(SubscriptionManager.ALLOWED_NETWORK_TYPES), anyString(), anyString()))
                 .thenReturn(null);
@@ -1545,6 +1565,83 @@
         assertEquals(false, mPhoneUT.isAllowedNetworkTypesLoadedFromDb());
     }
 
+    @Test
+    public void testLoadAllowedNetworksFromSubscriptionDatabase_allValidData() {
+        int subId = 1;
+        doReturn(subId).when(mSubscriptionManagerService).getSubId(anyInt());
+
+        // 13 == TelephonyManager.NETWORK_TYPE_LTE
+        // NR_BITMASK == 4096 == 1 << (13 - 1)
+        String validSerializedNetworkMap = "user=4096,power=4096,carrier=4096,enable_2g=4096";
+        SubscriptionInfoInternal si = new SubscriptionInfoInternal.Builder()
+                .setId(1)
+                .setAllowedNetworkTypesForReasons(validSerializedNetworkMap)
+                .build();
+        doReturn(si).when(mSubscriptionManagerService).getSubscriptionInfoInternal(eq(1));
+
+        assertFalse(mPhoneUT.isAllowedNetworkTypesLoadedFromDb());
+        mPhoneUT.loadAllowedNetworksFromSubscriptionDatabase();
+        assertTrue(mPhoneUT.isAllowedNetworkTypesLoadedFromDb());
+
+        for (int i = 0; i < 4; ++i) {
+            assertEquals(TelephonyManager.NETWORK_TYPE_BITMASK_LTE,
+                    mPhoneUT.getAllowedNetworkTypes(i));
+        }
+    }
+
+    @Test
+    public void testLoadAllowedNetworksFromSubscriptionDatabase_invalidKeys() {
+        int subId = 1;
+        doReturn(subId).when(mSubscriptionManagerService).getSubId(anyInt());
+
+        // 13 == TelephonyManager.NETWORK_TYPE_LTE
+        // NR_BITMASK == 4096 == 1 << (13 - 1)
+        String validSerializedNetworkMap =
+                "user=4096,power=4096,carrier=4096,enable_2g=4096,-1=4096";
+        SubscriptionInfoInternal si = new SubscriptionInfoInternal.Builder()
+                .setId(1)
+                .setAllowedNetworkTypesForReasons(validSerializedNetworkMap)
+                .build();
+        doReturn(si).when(mSubscriptionManagerService).getSubscriptionInfoInternal(eq(1));
+
+        assertFalse(mPhoneUT.isAllowedNetworkTypesLoadedFromDb());
+        mPhoneUT.loadAllowedNetworksFromSubscriptionDatabase();
+        assertTrue(mPhoneUT.isAllowedNetworkTypesLoadedFromDb());
+
+        for (int i = 0; i < 4; ++i) {
+            assertEquals(TelephonyManager.NETWORK_TYPE_BITMASK_LTE,
+                    mPhoneUT.getAllowedNetworkTypes(i));
+        }
+    }
+
+    @Test
+    public void testLoadAllowedNetworksFromSubscriptionDatabase_invalidValues() {
+        int subId = 1;
+        doReturn(subId).when(mSubscriptionManagerService).getSubId(anyInt());
+
+        // 19 == TelephonyManager.NETWORK_TYPE_NR
+        // NR_BITMASK == 524288 == 1 << 19
+        String validSerializedNetworkMap = "user=4096,power=4096,carrier=4096,enable_2g=-1";
+        SubscriptionInfoInternal si = new SubscriptionInfoInternal.Builder()
+                .setId(1)
+                .setAllowedNetworkTypesForReasons(validSerializedNetworkMap)
+                .build();
+        doReturn(si).when(mSubscriptionManagerService).getSubscriptionInfoInternal(eq(1));
+
+        mPhoneUT.loadAllowedNetworksFromSubscriptionDatabase();
+
+        for (int i = 0; i < 3; ++i) {
+            assertEquals(TelephonyManager.NETWORK_TYPE_BITMASK_LTE,
+                    mPhoneUT.getAllowedNetworkTypes(i));
+        }
+
+        long defaultAllowedNetworkTypes = RadioAccessFamily.getRafFromNetworkType(
+                RILConstants.PREFERRED_NETWORK_MODE);
+        assertEquals(defaultAllowedNetworkTypes, mPhoneUT.getAllowedNetworkTypes(
+                TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G));
+
+    }
+
     /**
      * Verifies that an emergency call placed on a SIM which does NOT explicitly define a number as
      * an emergency call will still be placed as an emergency call.
@@ -1625,7 +1722,7 @@
 
         doReturn(true).when(mTelephonyManager).isEmergencyNumber(anyString());
         doReturn(isEmergencyPerDialedSim).when(mEmergencyNumberTracker).isEmergencyNumber(
-                anyString(), anyBoolean());
+                anyString());
 
         mPhoneUT.setImsPhone(mImsPhone);
     }
@@ -1706,8 +1803,6 @@
         final SubscriptionInfoInternal si = makeSubscriptionInfoInternal(
                 false, SubscriptionManager.USAGE_SETTING_DATA_CENTRIC);
 
-        doReturn(si.toSubscriptionInfo()).when(mSubscriptionController)
-                .getSubscriptionInfo(anyInt());
         doReturn(si).when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
 
         mPhoneUT.updateUsageSetting();
@@ -1731,8 +1826,6 @@
 
         final SubscriptionInfoInternal si = makeSubscriptionInfoInternal(
                 true, SubscriptionManager.USAGE_SETTING_DEFAULT);
-        doReturn(si.toSubscriptionInfo()).when(mSubscriptionController)
-                .getSubscriptionInfo(anyInt());
         doReturn(si).when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
 
         mPhoneUT.updateUsageSetting();
@@ -1758,8 +1851,6 @@
                 false, SubscriptionManager.USAGE_SETTING_DEFAULT);
 
         assertNotNull(si);
-        doReturn(si.toSubscriptionInfo()).when(mSubscriptionController)
-                .getSubscriptionInfo(anyInt());
         doReturn(si).when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
 
         mPhoneUT.updateUsageSetting();
@@ -2069,7 +2160,7 @@
     }
 
     @Test
-    public void testDial_fdnCheck() throws Exception{
+    public void testDial_fdnCheck() throws Exception {
         // dial setup
         mSST.mSS = mServiceState;
         doReturn(ServiceState.STATE_IN_SERVICE).when(mServiceState).getState();
@@ -2097,7 +2188,7 @@
             connection = mPhoneUT.dial("1234567890",
                     new PhoneInternalInterface.DialArgs.Builder().build());
             fail("Expected CallStateException with ERROR_FDN_BLOCKED thrown.");
-        } catch(CallStateException e) {
+        } catch (CallStateException e) {
             assertEquals(CallStateException.ERROR_FDN_BLOCKED, e.getError());
         }
 
@@ -2105,8 +2196,383 @@
         fdnCheckCleanup();
     }
 
+    @Test
+    public void testHandleNullCipherAndIntegrityEnabled_radioFeatureUnsupported() {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CELLULAR_SECURITY,
+                TelephonyManager.PROPERTY_ENABLE_NULL_CIPHER_TOGGLE, Boolean.TRUE.toString(),
+                false);
+        mPhoneUT.mCi = mMockCi;
+        assertFalse(mPhoneUT.isNullCipherAndIntegritySupported());
+
+        mPhoneUT.sendMessage(mPhoneUT.obtainMessage(EVENT_RADIO_AVAILABLE,
+                new AsyncResult(null, new int[]{ServiceState.RIL_RADIO_TECHNOLOGY_GSM}, null)));
+        processAllMessages();
+
+        verify(mMockCi, times(1)).setNullCipherAndIntegrityEnabled(anyBoolean(),
+                any(Message.class));
+
+        // Some ephemeral error occurred in the modem, but the feature was supported
+        mPhoneUT.sendMessage(mPhoneUT.obtainMessage(EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE,
+                new AsyncResult(null, null,
+                        new CommandException(CommandException.Error.REQUEST_NOT_SUPPORTED))));
+        processAllMessages();
+        assertFalse(mPhoneUT.isNullCipherAndIntegritySupported());
+    }
+
+    @Test
+    public void testHandleNullCipherAndIntegrityEnabled_radioSupportsFeature() {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CELLULAR_SECURITY,
+                TelephonyManager.PROPERTY_ENABLE_NULL_CIPHER_TOGGLE, Boolean.TRUE.toString(),
+                false);
+        mPhoneUT.mCi = mMockCi;
+        assertFalse(mPhoneUT.isNullCipherAndIntegritySupported());
+
+        mPhoneUT.sendMessage(mPhoneUT.obtainMessage(EVENT_RADIO_AVAILABLE,
+                new AsyncResult(null, new int[]{ServiceState.RIL_RADIO_TECHNOLOGY_GSM}, null)));
+        processAllMessages();
+
+        verify(mMockCi, times(1)).setNullCipherAndIntegrityEnabled(anyBoolean(),
+                any(Message.class));
+
+        // Some ephemeral error occurred in the modem, but the feature was supported
+        mPhoneUT.sendMessage(mPhoneUT.obtainMessage(EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE,
+                new AsyncResult(null, null, null)));
+        processAllMessages();
+        assertTrue(mPhoneUT.isNullCipherAndIntegritySupported());
+    }
+
+    @Test
+    public void testHandleNullCipherAndIntegrityEnabled_featureFlagOn() {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CELLULAR_SECURITY,
+                TelephonyManager.PROPERTY_ENABLE_NULL_CIPHER_TOGGLE, Boolean.TRUE.toString(),
+                false);
+        mPhoneUT.mCi = mMockCi;
+
+        mPhoneUT.sendMessage(mPhoneUT.obtainMessage(EVENT_RADIO_AVAILABLE,
+                new AsyncResult(null, new int[]{ServiceState.RIL_RADIO_TECHNOLOGY_GSM}, null)));
+        processAllMessages();
+
+        verify(mMockCi, times(1)).setNullCipherAndIntegrityEnabled(anyBoolean(),
+                any(Message.class));
+    }
+
+    @Test
+    public void testHandleNullCipherAndIntegrityEnabled_featureFlagOff() {
+        mPhoneUT.mCi = mMockCi;
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CELLULAR_SECURITY,
+                TelephonyManager.PROPERTY_ENABLE_NULL_CIPHER_TOGGLE, Boolean.FALSE.toString(),
+                false);
+
+        mPhoneUT.sendMessage(mPhoneUT.obtainMessage(EVENT_RADIO_AVAILABLE,
+                new AsyncResult(null, new int[]{ServiceState.RIL_RADIO_TECHNOLOGY_GSM}, null)));
+        processAllMessages();
+
+        verify(mMockCi, times(0)).setNullCipherAndIntegrityEnabled(anyBoolean(),
+                any(Message.class));
+    }
+
     public void fdnCheckCleanup() {
         doReturn(false).when(mUiccCardApplication3gpp).getIccFdnAvailable();
         doReturn(false).when(mUiccCardApplication3gpp).getIccFdnEnabled();
     }
+
+    @Test
+    @SmallTest
+    public void testTriggerImsDeregistration() throws Exception {
+        replaceInstance(Phone.class, "mImsPhone", mPhoneUT, mImsPhone);
+
+        mPhoneUT.sendMessage(mPhoneUT.obtainMessage(EVENT_IMS_DEREGISTRATION_TRIGGERED,
+                new AsyncResult(null,
+                        new int[]{ImsRegistrationImplBase.REASON_SIM_REFRESH}, null)));
+        processAllMessages();
+
+        verify(mImsPhone, times(1)).triggerImsDeregistration(
+                eq(ImsRegistrationImplBase.REASON_SIM_REFRESH));
+    }
+
+    @Test
+    public void testDomainSelectionEmergencyCallCs() throws CallStateException {
+        setupEmergencyCallScenario(false /* USE_ONLY_DIALED_SIM_ECC_LIST */,
+                false /* isEmergencyOnDialedSim */);
+
+        Bundle extras = new Bundle();
+        extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, NetworkRegistrationInfo.DOMAIN_CS);
+        ImsPhone.ImsDialArgs dialArgs = new ImsPhone.ImsDialArgs.Builder()
+                .setIntentExtras(extras)
+                .build();
+        mPhoneUT.dial(TEST_EMERGENCY_NUMBER, dialArgs);
+
+        verify(mCT).dialGsm(anyString(), any(PhoneInternalInterface.DialArgs.class));
+    }
+
+    @Test
+    public void testDomainSelectionEmergencyCallPs() throws CallStateException {
+        setupEmergencyCallScenario(false /* USE_ONLY_DIALED_SIM_ECC_LIST */,
+                false /* isEmergencyOnDialedSim */);
+
+        doReturn(false).when(mImsPhone).isImsAvailable();
+
+        Bundle extras = new Bundle();
+        extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, NetworkRegistrationInfo.DOMAIN_PS);
+        ImsPhone.ImsDialArgs dialArgs = new ImsPhone.ImsDialArgs.Builder()
+                .setIntentExtras(extras)
+                .build();
+        mPhoneUT.dial(TEST_EMERGENCY_NUMBER, dialArgs);
+
+        verify(mImsPhone).dial(anyString(), any(PhoneInternalInterface.DialArgs.class));
+
+        extras = dialArgs.intentExtras;
+
+        assertFalse(extras.containsKey(ImsCallProfile.EXTRA_CALL_RAT_TYPE));
+    }
+
+    @Test
+    public void testDomainSelectionEmergencyCallNon3GppPs() throws CallStateException {
+        setupEmergencyCallScenario(false /* USE_ONLY_DIALED_SIM_ECC_LIST */,
+                false /* isEmergencyOnDialedSim */);
+
+        doReturn(false).when(mImsPhone).isImsAvailable();
+
+        Bundle extras = new Bundle();
+        extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, PhoneConstants.DOMAIN_NON_3GPP_PS);
+        ImsPhone.ImsDialArgs dialArgs = new ImsPhone.ImsDialArgs.Builder()
+                .setIntentExtras(extras)
+                .build();
+        mPhoneUT.dial(TEST_EMERGENCY_NUMBER, dialArgs);
+
+        verify(mImsPhone).dial(anyString(), any(PhoneInternalInterface.DialArgs.class));
+
+        extras = dialArgs.intentExtras;
+
+        assertTrue(extras.containsKey(ImsCallProfile.EXTRA_CALL_RAT_TYPE));
+        assertEquals(String.valueOf(ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN),
+                extras.getString(ImsCallProfile.EXTRA_CALL_RAT_TYPE));
+    }
+
+    @Test
+    public void testDomainSelectionDialCs() throws Exception {
+        doReturn(true).when(mImsPhone).isImsAvailable();
+        doReturn(true).when(mImsManager).isVolteEnabledByPlatform();
+        doReturn(true).when(mImsManager).isEnhanced4gLteModeSettingEnabledByUser();
+        doReturn(true).when(mImsManager).isNonTtyOrTtyOnVolteEnabled();
+        doReturn(true).when(mImsPhone).isVoiceOverCellularImsEnabled();
+        doReturn(true).when(mImsPhone).isUtEnabled();
+
+        replaceInstance(Phone.class, "mImsPhone", mPhoneUT, mImsPhone);
+
+        Bundle extras = new Bundle();
+        extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, NetworkRegistrationInfo.DOMAIN_CS);
+        ImsPhone.ImsDialArgs dialArgs = new ImsPhone.ImsDialArgs.Builder()
+                .setIntentExtras(extras)
+                .build();
+        Connection connection = mPhoneUT.dial("1234567890", dialArgs);
+        verify(mCT).dialGsm(eq("1234567890"), any(PhoneInternalInterface.DialArgs.class));
+    }
+
+    @Test
+    public void testDomainSelectionDialPs() throws Exception {
+        mSST.mSS = mServiceState;
+        doReturn(ServiceState.STATE_IN_SERVICE).when(mServiceState).getState();
+
+        mCT.mForegroundCall = mGsmCdmaCall;
+        mCT.mBackgroundCall = mGsmCdmaCall;
+        mCT.mRingingCall = mGsmCdmaCall;
+        doReturn(GsmCdmaCall.State.IDLE).when(mGsmCdmaCall).getState();
+
+        replaceInstance(Phone.class, "mImsPhone", mPhoneUT, mImsPhone);
+
+        Bundle extras = new Bundle();
+        extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, NetworkRegistrationInfo.DOMAIN_PS);
+        ImsPhone.ImsDialArgs dialArgs = new ImsPhone.ImsDialArgs.Builder()
+                .setIntentExtras(extras)
+                .build();
+        Connection connection = mPhoneUT.dial("1234567890", dialArgs);
+        verify(mImsPhone).dial(eq("1234567890"), any(PhoneInternalInterface.DialArgs.class));
+    }
+
+    @Test
+    public void getImeiType_primary() {
+        Message message = mPhoneUT.obtainMessage(Phone.EVENT_GET_DEVICE_IMEI_DONE);
+        ImeiInfo imeiInfo = new ImeiInfo();
+        imeiInfo.imei = FAKE_IMEI;
+        imeiInfo.svn = FAKE_IMEISV;
+        imeiInfo.type = ImeiInfo.ImeiType.PRIMARY;
+        AsyncResult.forMessage(message, imeiInfo, null);
+        mPhoneUT.handleMessage(message);
+        assertEquals(Phone.IMEI_TYPE_PRIMARY, mPhoneUT.getImeiType());
+        assertEquals(FAKE_IMEI, mPhoneUT.getImei());
+    }
+
+    @Test
+    public void getImeiType_Secondary() {
+        Message message = mPhoneUT.obtainMessage(Phone.EVENT_GET_DEVICE_IMEI_DONE);
+        ImeiInfo imeiInfo = new ImeiInfo();
+        imeiInfo.imei = FAKE_IMEI;
+        imeiInfo.svn = FAKE_IMEISV;
+        imeiInfo.type = ImeiInfo.ImeiType.SECONDARY;
+        AsyncResult.forMessage(message, imeiInfo, null);
+        mPhoneUT.handleMessage(message);
+        assertEquals(Phone.IMEI_TYPE_SECONDARY, mPhoneUT.getImeiType());
+        assertEquals(FAKE_IMEI, mPhoneUT.getImei());
+    }
+
+    @Test
+    public void getImei() {
+        assertTrue(mPhoneUT.isPhoneTypeGsm());
+        Message message = mPhoneUT.obtainMessage(Phone.EVENT_RADIO_AVAILABLE);
+        mPhoneUT.handleMessage(message);
+        verify(mSimulatedCommandsVerifier, times(2)).getImei(nullable(Message.class));
+    }
+
+    @Test
+    public void testSetAllowedNetworkTypes_admin2gRestrictionHonored() throws Exception {
+        // circumvent loading/saving to sim db. it's not behavior under test.
+        TelephonyManager.setupISubForTest(Mockito.mock(SubscriptionManagerService.class));
+        TelephonyManager.enableServiceHandleCaching();
+        mPhoneUT.loadAllowedNetworksFromSubscriptionDatabase();
+
+        // 2g is disabled by admin
+        UserManager userManagerMock = mContext.getSystemService(UserManager.class);
+        when(userManagerMock.hasUserRestriction(UserManager.DISALLOW_CELLULAR_2G)).thenReturn(true);
+        mContext.sendBroadcast(new Intent(UserManager.ACTION_USER_RESTRICTIONS_CHANGED));
+
+        // carrier requests 2g to be enabled
+        mPhoneUT.setAllowedNetworkTypes(TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER,
+                TelephonyManager.NETWORK_CLASS_BITMASK_2G, null);
+
+        // Assert that 2g was not passed as an allowed network type to the modem
+        ArgumentCaptor<Integer> captureBitMask = ArgumentCaptor.forClass(Integer.class);
+        // One call for the admin restriction update, another by this test
+        verify(mSimulatedCommandsVerifier, times(2)).setAllowedNetworkTypesBitmap(
+                captureBitMask.capture(),
+                nullable(Message.class));
+        assertEquals(0, captureBitMask.getValue() & TelephonyManager.NETWORK_CLASS_BITMASK_2G);
+    }
+
+    @Test
+    @SmallTest
+    public void testEcbm() throws Exception {
+        assertFalse(mPhoneUT.isInEcm());
+
+        mPhoneUT.handleMessage(mPhoneUT.obtainMessage(
+                GsmCdmaPhone.EVENT_EMERGENCY_CALLBACK_MODE_ENTER));
+
+        assertTrue(mPhoneUT.isInEcm());
+        verifyEcbmIntentWasSent(1 /*times*/, true /*inEcm*/);
+        // verify that wakeLock is acquired in ECM
+        assertTrue(mPhoneUT.getWakeLock().isHeld());
+
+        boolean isTestingEmergencyCallbackMode = true;
+        replaceInstance(GsmCdmaPhone.class, "mIsTestingEmergencyCallbackMode", mPhoneUT,
+                isTestingEmergencyCallbackMode);
+        mPhoneUT.exitEmergencyCallbackMode();
+        processAllMessages();
+
+        assertFalse(mPhoneUT.isInEcm());
+        verifyEcbmIntentWasSent(2/*times*/, false /*inEcm*/);
+        // verify wakeLock released
+        assertFalse(mPhoneUT.getWakeLock().isHeld());
+    }
+
+    private void verifyEcbmIntentWasSent(int times, boolean isInEcm) throws Exception {
+        // verify ACTION_EMERGENCY_CALLBACK_MODE_CHANGED
+        ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, atLeast(times)).sendStickyBroadcastAsUser(intentArgumentCaptor.capture(),
+                any());
+
+        Intent intent = intentArgumentCaptor.getValue();
+        assertNotNull(intent);
+        assertEquals(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED, intent.getAction());
+        assertEquals(isInEcm, intent.getBooleanExtra(
+                TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false));
+    }
+
+    @Test
+    @SmallTest
+    public void testEcbmWhenDomainSelectionEnabled() throws Exception {
+        DomainSelectionResolver dsResolver = Mockito.mock(DomainSelectionResolver.class);
+        doReturn(true).when(dsResolver).isDomainSelectionSupported();
+        DomainSelectionResolver.setDomainSelectionResolver(dsResolver);
+
+        mPhoneUT.handleMessage(mPhoneUT.obtainMessage(
+                GsmCdmaPhone.EVENT_EMERGENCY_CALLBACK_MODE_ENTER));
+
+        boolean isTestingEmergencyCallbackMode = true;
+        replaceInstance(GsmCdmaPhone.class, "mIsTestingEmergencyCallbackMode", mPhoneUT,
+                isTestingEmergencyCallbackMode);
+        mPhoneUT.exitEmergencyCallbackMode();
+        processAllMessages();
+
+        verify(mContext, never()).sendStickyBroadcastAsUser(any(), any());
+    }
+
+    @Test
+    @SmallTest
+    public void testEcbmOnModemResetForNonGsmPhone() throws Exception {
+        switchToCdma();
+        assertFalse(mPhoneUT.isInEcm());
+
+        mPhoneUT.handleMessage(mPhoneUT.obtainMessage(
+                GsmCdmaPhone.EVENT_EMERGENCY_CALLBACK_MODE_ENTER));
+
+        assertTrue(mPhoneUT.isInEcm());
+
+        Message m = mPhoneUT.obtainMessage(GsmCdmaPhone.EVENT_MODEM_RESET);
+        AsyncResult.forMessage(m);
+        mPhoneUT.handleMessage(m);
+
+        assertFalse(mPhoneUT.isInEcm());
+        verifyEcbmIntentWasSent(2 /*times*/, false /*inEcm*/);
+    }
+
+    @Test
+    @SmallTest
+    public void testEcbmOnModemResetWhenDomainSelectionEnabled() throws Exception {
+        DomainSelectionResolver dsResolver = Mockito.mock(DomainSelectionResolver.class);
+        doReturn(true).when(dsResolver).isDomainSelectionSupported();
+        DomainSelectionResolver.setDomainSelectionResolver(dsResolver);
+
+        EmergencyStateTracker est = Mockito.mock(EmergencyStateTracker.class);
+        doReturn(true).when(est).isInEcm();
+        replaceInstance(EmergencyStateTracker.class, "INSTANCE", null, est);
+
+        GsmCdmaPhone spyPhone = spy(mPhoneUT);
+        doReturn(true).when(spyPhone).isInEcm();
+        mPhoneUT.handleMessage(mPhoneUT.obtainMessage(GsmCdmaPhone.EVENT_MODEM_RESET));
+
+        verify(est).exitEmergencyCallbackMode();
+    }
+
+    @Test
+    public void testGetUserHandle() {
+        UserHandle userHandle = new UserHandle(123);
+        doReturn(userHandle).when(mSubscriptionManager).getSubscriptionUserHandle(anyInt());
+        assertEquals(userHandle, mPhoneUT.getUserHandle());
+
+        doReturn(null).when(mSubscriptionManager).getSubscriptionUserHandle(anyInt());
+        assertNull(mPhoneUT.getUserHandle());
+
+        doThrow(IllegalArgumentException.class).when(mSubscriptionManager)
+                .getSubscriptionUserHandle(anyInt());
+        assertNull(mPhoneUT.getUserHandle());
+    }
+
+    @Test
+    public void testResetNetworkSelectionModeOnSimSwap() {
+        // Set current network selection manual mode.
+        mSimulatedCommands.setNetworkSelectionModeManual("123", 0, null);
+        clearInvocations(mSimulatedCommandsVerifier);
+
+        // SIM loaded.
+        Intent simLoadedIntent = new Intent(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED);
+        simLoadedIntent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, mPhone.getPhoneId());
+        simLoadedIntent.putExtra(TelephonyManager.EXTRA_SIM_STATE,
+                TelephonyManager.SIM_STATE_LOADED);
+        mContext.sendBroadcast(simLoadedIntent);
+
+        processAllFutureMessages();
+        // Verify set network selection mode to be AUTO
+        verify(mSimulatedCommandsVerifier).getNetworkSelectionMode(any(Message.class));
+        verify(mSimulatedCommandsVerifier).setNetworkSelectionModeAutomatic(any(Message.class));
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/IccSmsInterfaceManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/IccSmsInterfaceManagerTest.java
index 29b128d..4012e98 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/IccSmsInterfaceManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/IccSmsInterfaceManagerTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -33,6 +34,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.telephony.emergency.EmergencyNumberTracker;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -50,6 +53,8 @@
 
     // Mocked classes
     private SmsPermissions mMockSmsPermissions;
+    protected EmergencyNumberTracker mEmergencyNumberTracker2;
+    protected IccSmsInterfaceManager.PhoneFactoryProxy mPhoneFactoryProxy;
 
     @Before
     public void setUp() throws Exception {
@@ -57,6 +62,12 @@
         mMockSmsPermissions = mock(SmsPermissions.class);
         mIccSmsInterfaceManager = new IccSmsInterfaceManager(mPhone, mContext, mAppOpsManager,
                 mSmsDispatchersController, mMockSmsPermissions);
+
+        mPhoneFactoryProxy = mock(IccSmsInterfaceManager.PhoneFactoryProxy.class);
+        mIccSmsInterfaceManager.setPhoneFactoryProxy(mPhoneFactoryProxy);
+
+        mEmergencyNumberTracker2 = mock(EmergencyNumberTracker.class);
+        doReturn(mEmergencyNumberTracker2).when(mPhone2).getEmergencyNumberTracker();
     }
 
     @After
@@ -144,4 +155,22 @@
             fail("getSmscLatch.await interrupted");
         }
     }
+
+    @Test
+    public void testNotifyIfOutgoingEmergencySmsWithDualSim() {
+        //Replicate Dual-SIM:
+        Phone [] phones = {mPhone, mPhone2};
+        when(mPhoneFactoryProxy.getPhones()).thenReturn(phones);
+        doReturn(1).when(mPhone).getPhoneId();
+        doReturn(2).when(mPhone2).getPhoneId();
+
+        //Replicate behavior when a number is an emergency number
+        // on the secondary SIM but not on the default SIM:
+        when(mPhone.getEmergencyNumberTracker().getEmergencyNumber(any())).thenReturn(null);
+        when(mEmergencyNumberTracker2.getEmergencyNumber(any()))
+                .thenReturn(getTestEmergencyNumber());
+
+        mIccSmsInterfaceManager.notifyIfOutgoingEmergencySms("1234");
+        verify(mEmergencyNumberTracker2).getEmergencyNumber("1234");
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java
index fd1c5e0..1c44772 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java
@@ -29,10 +29,13 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
 import android.telephony.SmsMessage;
 import android.telephony.ims.stub.ImsSmsImplBase;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -64,7 +67,9 @@
     private FeatureConnector.Listener<ImsManager> mImsManagerListener;
     private HashMap<String, Object> mTrackerData;
     private ImsSmsDispatcher mImsSmsDispatcher;
+    PersistableBundle mBundle = new PersistableBundle();
     private static final int SUB_0 = 0;
+    private static final String TAG = "ImsSmsDispatcherTest";
 
     @Before
     public void setUp() throws Exception {
@@ -87,6 +92,7 @@
         when(mSmsDispatchersController.isIms()).thenReturn(true);
         mTrackerData = new HashMap<>(1);
         when(mSmsTracker.getData()).thenReturn(mTrackerData);
+        verify(mSmsDispatchersController).setImsManager(mImsManager);
     }
 
     @After
@@ -97,6 +103,85 @@
         super.tearDown();
     }
 
+   /**
+     * Send Memory Availability Notification and verify that the token is correct.
+     */
+    @Test
+    @SmallTest
+    public void testOnMemoryAvailable() throws Exception {
+        int token = mImsSmsDispatcher.mNextToken.get();
+        //Send SMMA
+        mImsSmsDispatcher.onMemoryAvailable();
+        assertEquals(token + 1, mImsSmsDispatcher.mNextToken.get());
+        verify(mImsManager).onMemoryAvailable(eq(token + 1));
+    }
+
+    /**
+     * Receive SEND_STATUS_ERROR_RETRY with onMemoryAvailableResult Api and check if
+     * sending SMMA Notification is retried once
+     */
+    @Test
+    @SmallTest
+    public void testOnMemoryAvailableResultErrorRetry() throws Exception {
+        int token = mImsSmsDispatcher.mNextToken.get();
+        //Send SMMA
+        mImsSmsDispatcher.onMemoryAvailable();
+        assertEquals(token + 1, mImsSmsDispatcher.mNextToken.get());
+        verify(mImsManager).onMemoryAvailable(eq(token + 1));
+        // Retry over IMS
+        mImsSmsDispatcher.getSmsListener().onMemoryAvailableResult(token + 1,
+                ImsSmsImplBase.SEND_STATUS_ERROR_RETRY, SmsResponse.NO_ERROR_CODE);
+        waitForMs(SMSDispatcher.SEND_RETRY_DELAY + 200);
+        processAllMessages();
+        verify(mImsManager).onMemoryAvailable(eq(token + 2));
+        //2nd Failure should not retry
+        mImsSmsDispatcher.getSmsListener().onMemoryAvailableResult(token + 2,
+                ImsSmsImplBase.SEND_STATUS_ERROR_RETRY, SmsResponse.NO_ERROR_CODE);
+        waitForMs(SMSDispatcher.SEND_RETRY_DELAY + 200);
+        processAllMessages();
+        verify(mImsManager, times(0)).onMemoryAvailable(eq(token + 3));
+
+    }
+    /**
+     * Receive SEND_STATUS_OK with onMemoryAvailableResult Api and check if
+     * sending SMMA Notification behaviour is correct
+     */
+    @Test
+    @SmallTest
+    public void testOnMemoryAvailableResultSuccess() throws Exception {
+        int token = mImsSmsDispatcher.mNextToken.get();
+        //Send SMMA
+        mImsSmsDispatcher.onMemoryAvailable();
+        assertEquals(token + 1, mImsSmsDispatcher.mNextToken.get());
+        verify(mImsManager).onMemoryAvailable(eq(token + 1));
+        // Retry over IMS
+        mImsSmsDispatcher.getSmsListener().onMemoryAvailableResult(token + 1,
+                ImsSmsImplBase.SEND_STATUS_OK, SmsResponse.NO_ERROR_CODE);
+        waitForMs(SMSDispatcher.SEND_RETRY_DELAY + 200);
+        processAllMessages();
+        verify(mImsManager, times(0)).onMemoryAvailable(eq(token + 2));
+
+    }
+    /**
+     * Receive SEND_STATUS_ERROR with onMemoryAvailableResult Api and check if
+     * sending SMMA Notification behaviour is correct
+     */
+    @Test
+    @SmallTest
+    public void testOnMemoryAvailableResultError() throws Exception {
+        int token = mImsSmsDispatcher.mNextToken.get();
+        //Send SMMA
+        mImsSmsDispatcher.onMemoryAvailable();
+        assertEquals(token + 1, mImsSmsDispatcher.mNextToken.get());
+        verify(mImsManager).onMemoryAvailable(eq(token + 1));
+        // Retry over IMS
+        mImsSmsDispatcher.getSmsListener().onMemoryAvailableResult(token + 1,
+                ImsSmsImplBase.SEND_STATUS_ERROR, SmsResponse.NO_ERROR_CODE);
+        waitForMs(SMSDispatcher.SEND_RETRY_DELAY + 200);
+        processAllMessages();
+        verify(mImsManager, times(0)).onMemoryAvailable(eq(token + 2));
+
+    }
     /**
      * Send an SMS and verify that the token and PDU is correct.
      */
@@ -155,6 +240,13 @@
     @SmallTest
     public void testErrorImsRetry() throws Exception {
         int token = mImsSmsDispatcher.mNextToken.get();
+        mContextFixture.getCarrierConfigBundle().putInt(CarrierConfigManager.ImsSms
+                                .KEY_SMS_OVER_IMS_SEND_RETRY_DELAY_MILLIS_INT,
+                                2000);
+        mContextFixture.getCarrierConfigBundle().putInt(CarrierConfigManager.ImsSms
+                                                .KEY_SMS_MAX_RETRY_OVER_IMS_COUNT_INT, 3);
+        mContextFixture.getCarrierConfigBundle().putInt(CarrierConfigManager.ImsSms
+                                                .KEY_SMS_MAX_RETRY_COUNT_INT, 3);
         mTrackerData.put("pdu", com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(null,
                 "+15555551212", "Test", false).encodedMessage);
         when(mImsManager.getSmsFormat()).thenReturn(SmsMessage.FORMAT_3GPP);
@@ -230,7 +322,10 @@
     @Test
     public void testSendSmswithMessageRef() throws Exception {
         int token = mImsSmsDispatcher.mNextToken.get();
-        int messageRef = mImsSmsDispatcher.nextMessageRef() + 1;
+        int messageRef = mImsSmsDispatcher.nextMessageRef();
+        if (mImsSmsDispatcher.isMessageRefIncrementViaTelephony()) {
+            messageRef += 1;
+        }
 
         when(mImsManager.getSmsFormat()).thenReturn(SmsMessage.FORMAT_3GPP);
         when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_GSM);
@@ -246,7 +341,10 @@
     @Test
     public void testFallbackGsmRetrywithMessageRef() throws Exception {
         int token = mImsSmsDispatcher.mNextToken.get();
-        int messageRef = mImsSmsDispatcher.nextMessageRef() + 1;
+        int messageRef = mImsSmsDispatcher.nextMessageRef();
+        if (mImsSmsDispatcher.isMessageRefIncrementViaTelephony()) {
+            messageRef += 1;
+        }
 
         when(mImsManager.getSmsFormat()).thenReturn(SmsMessage.FORMAT_3GPP);
         when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_GSM);
@@ -265,13 +363,28 @@
         ArgumentCaptor<SMSDispatcher.SmsTracker> captor =
                 ArgumentCaptor.forClass(SMSDispatcher.SmsTracker.class);
         verify(mSmsDispatchersController).sendRetrySms(captor.capture());
-        assertTrue(messageRef + 1 == captor.getValue().mMessageRef);
+        if (mImsSmsDispatcher.isMessageRefIncrementViaTelephony()) {
+            assertTrue(messageRef + 1 == captor.getValue().mMessageRef);
+        } else {
+            assertTrue(messageRef == captor.getValue().mMessageRef);
+        }
     }
 
     @Test
     public void testErrorImsRetrywithMessageRef() throws Exception {
         int token = mImsSmsDispatcher.mNextToken.get();
-        int messageRef = mImsSmsDispatcher.nextMessageRef() + 1;
+        int messageRef = mImsSmsDispatcher.nextMessageRef();
+        if (mImsSmsDispatcher.isMessageRefIncrementViaTelephony()) {
+            messageRef += 1;
+        }
+
+        mContextFixture.getCarrierConfigBundle().putInt(CarrierConfigManager.ImsSms
+                                                .KEY_SMS_OVER_IMS_SEND_RETRY_DELAY_MILLIS_INT,
+                                                2000);
+        mContextFixture.getCarrierConfigBundle().putInt(CarrierConfigManager.ImsSms
+                                                .KEY_SMS_MAX_RETRY_OVER_IMS_COUNT_INT, 3);
+        mContextFixture.getCarrierConfigBundle().putInt(CarrierConfigManager.ImsSms
+                                                .KEY_SMS_MAX_RETRY_COUNT_INT, 3);
         when(mImsManager.getSmsFormat()).thenReturn(SmsMessage.FORMAT_3GPP);
         when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_GSM);
         doReturn(mSmsUsageMonitor).when(mSmsDispatchersController).getUsageMonitor();
@@ -293,7 +406,73 @@
         verify(mImsManager).sendSms(eq(token + 2), eq(messageRef), eq(SmsMessage.FORMAT_3GPP),
                 nullable(String.class), eq(true), byteCaptor.capture());
         byte[] pdu = byteCaptor.getValue();
-        assertEquals(pdu[1], messageRef);
+        assertEquals(messageRef, pdu[1]);
         assertEquals(0x04, (pdu[0] & 0x04));
     }
+
+    @Test
+    public void testErrorImsRetrywithRetryConfig() throws Exception {
+        int token = mImsSmsDispatcher.mNextToken.get();
+        int messageRef = mImsSmsDispatcher.nextMessageRef();
+        if (mImsSmsDispatcher.isMessageRefIncrementViaTelephony()) {
+            messageRef += 1;
+        }
+
+        mContextFixture.getCarrierConfigBundle().putInt(CarrierConfigManager.ImsSms
+                                                .KEY_SMS_OVER_IMS_SEND_RETRY_DELAY_MILLIS_INT,
+                                                3000);
+        mContextFixture.getCarrierConfigBundle().putInt(CarrierConfigManager.ImsSms
+                                                .KEY_SMS_MAX_RETRY_OVER_IMS_COUNT_INT, 2);
+        mContextFixture.getCarrierConfigBundle().putInt(CarrierConfigManager.ImsSms
+                                                .KEY_SMS_MAX_RETRY_COUNT_INT, 3);
+        when(mImsManager.getSmsFormat()).thenReturn(SmsMessage.FORMAT_3GPP);
+        when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_GSM);
+        doReturn(mSmsUsageMonitor).when(mSmsDispatchersController).getUsageMonitor();
+        mImsSmsDispatcher.sendText("+15555551212", null, "Retry test",
+                null, null, null, null, false,
+                -1, false, -1, false, 0);
+        verify(mImsManager).sendSms(eq(token + 1), eq(messageRef), eq(SmsMessage.FORMAT_3GPP),
+                nullable(String.class), eq(false), (byte[]) any());
+        assertEquals(2, mImsSmsDispatcher.getMaxRetryCountOverIms());
+        assertEquals(3, mImsSmsDispatcher.getMaxSmsRetryCount());
+        assertEquals(3000, mImsSmsDispatcher.getSmsRetryDelayValue());
+        // Retry over IMS
+        mImsSmsDispatcher.getSmsListener().onSendSmsResult(token + 1, messageRef,
+                ImsSmsImplBase.SEND_STATUS_ERROR_RETRY, 0, SmsResponse.NO_ERROR_CODE);
+        waitForMs(mImsSmsDispatcher.getSmsRetryDelayValue() + 200);
+        processAllMessages();
+
+        // Make sure tpmr value is same and retry bit set
+        ArgumentCaptor<byte[]> byteCaptor = ArgumentCaptor.forClass(byte[].class);
+        verify(mImsManager).sendSms(eq(token + 2), eq(messageRef), eq(SmsMessage.FORMAT_3GPP),
+                nullable(String.class), eq(true), byteCaptor.capture());
+        byte[] pdu = byteCaptor.getValue();
+        assertEquals(messageRef, pdu[1]);
+        assertEquals(0x04, (pdu[0] & 0x04));
+        // Retry over IMS for the second time
+        mImsSmsDispatcher.getSmsListener().onSendSmsResult(token + 2, messageRef,
+                ImsSmsImplBase.SEND_STATUS_ERROR_RETRY, 0, SmsResponse.NO_ERROR_CODE);
+        waitForMs(mImsSmsDispatcher.getSmsRetryDelayValue() + 200);
+        processAllMessages();
+        // Make sure tpmr value is same and retry bit set
+        ArgumentCaptor<byte[]> byteCaptor2 = ArgumentCaptor.forClass(byte[].class);
+        verify(mImsManager).sendSms(eq(token + 3), eq(messageRef), eq(SmsMessage.FORMAT_3GPP),
+                nullable(String.class), eq(true), byteCaptor2.capture());
+        byte[] pdu2 = byteCaptor2.getValue();
+        assertEquals(messageRef, pdu2[1]);
+        assertEquals(0x04, (pdu2[0] & 0x04));
+
+        mImsSmsDispatcher.getSmsListener().onSendSmsResult(token + 3, messageRef,
+                ImsSmsImplBase.SEND_STATUS_ERROR_RETRY, 0, SmsResponse.NO_ERROR_CODE);
+        waitForMs(mImsSmsDispatcher.getSmsRetryDelayValue() + 200);
+        processAllMessages();
+        // Make sure tpmr value is same and retry bit set
+        ArgumentCaptor<SMSDispatcher.SmsTracker> captor =
+                ArgumentCaptor.forClass(SMSDispatcher.SmsTracker.class);
+        // Ensure GsmSmsDispatcher calls sendSms
+        verify(mSmsDispatchersController).sendRetrySms(captor.capture());
+
+        assertNotNull(captor.getValue());
+        assertTrue(captor.getValue().mRetryCount > 0);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/InboundSmsTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/InboundSmsTrackerTest.java
index b11b94e..d251cf5 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/InboundSmsTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/InboundSmsTrackerTest.java
@@ -24,8 +24,6 @@
 import android.database.MatrixCursor;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import androidx.test.InstrumentationRegistry;
-
 import com.android.internal.util.HexDump;
 
 import org.junit.After;
@@ -34,7 +32,7 @@
 
 import java.util.Arrays;
 
-public class InboundSmsTrackerTest {
+public class InboundSmsTrackerTest extends TelephonyTest {
     InboundSmsTracker mInboundSmsTracker;
 
     private static final byte[] FAKE_PDU = new byte[]{1, 2, 3};
@@ -50,7 +48,8 @@
 
     @Before
     public void setUp() throws Exception {
-        mInboundSmsTracker = new InboundSmsTracker(InstrumentationRegistry.getContext(),
+        super.setUp(getClass().getSimpleName());
+        mInboundSmsTracker = new InboundSmsTracker(mContext,
                 FAKE_PDU, FAKE_TIMESTAMP, FAKE_DEST_PORT, false,
                 FAKE_ADDRESS, FAKE_DISPLAY_ADDRESS, FAKE_REFERENCE_NUMBER, FAKE_SEQUENCE_NUMBER,
                 FAKE_MESSAGE_COUNT, false, FAKE_MESSAGE_BODY, false /* isClass0 */, FAKE_SUBID,
@@ -58,8 +57,9 @@
     }
 
     @After
-    public void tearDown() {
+    public void tearDown() throws Exception {
         mInboundSmsTracker = null;
+        super.tearDown();
     }
 
     public static MatrixCursor createFakeCursor() {
@@ -108,8 +108,7 @@
     @SmallTest
     public void testInitializationFromDb() {
         Cursor cursor = createFakeCursor();
-        mInboundSmsTracker = new InboundSmsTracker(InstrumentationRegistry.getContext(),
-                cursor, false);
+        mInboundSmsTracker = new InboundSmsTracker(mContext, cursor, false);
         cursor.close();
         testInitialization();
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java
index 372733d..f4c19d9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java
@@ -136,6 +136,7 @@
         mSubInfo[subId] = new SubscriptionInfoInternal.Builder(mSubInfo[subId])
                 .setSimSlotIndex(simSlotIndex).build();
     }
+
     private void sendCarrierConfigChanged(int phoneId, int subId) {
         mCarrierConfigChangeListener.onCarrierConfigChanged(phoneId, subId,
                 TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
@@ -144,7 +145,6 @@
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
-        enableSubscriptionManagerService(true);
         initializeSubs();
         mPhoneMock1 = mock(Phone.class);
         mPhoneMock2 = mock(Phone.class);
@@ -156,6 +156,9 @@
         doReturn(mPhone).when(mPhone).getImsPhone();
         mServiceManagerMockedServices.put("isub", mIBinder);
 
+        doReturn(mSubscriptionManagerService).when(mIBinder)
+                .queryLocalInterface(anyString());
+
         // Default configuration:
         // DSDS device.
         // Sub 1 is the default sub.
@@ -243,12 +246,10 @@
         }).when(mPhoneMock2).getSubId();
 
         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,
-                mSubscriptionController);
+        mMultiSimSettingControllerUT = new MultiSimSettingController(mContext);
         processAllMessages();
         verify(mCarrierConfigManager).registerCarrierConfigChangeListener(any(),
                 listenerArgumentCaptor.capture());
@@ -463,15 +464,15 @@
         markSubscriptionInactive(1);
         mMultiSimSettingControllerUT.notifySubscriptionInfoChanged();
         processAllMessages();
+        sendCarrierConfigChanged(1, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        processAllMessages();
+
         verify(mSubscriptionManagerService).setDefaultDataSubId(
                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
         verify(mSubscriptionManagerService).setDefaultSmsSubId(
                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
         verify(mSubscriptionManagerService, never()).setDefaultVoiceSubId(anyInt());
 
-        sendCarrierConfigChanged(1, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-        processAllMessages();
-
         // Verify intent sent to select sub 2 as default for all types.
         Intent intent = captureBroadcastIntent();
         assertEquals(ACTION_PRIMARY_SUBSCRIPTION_LIST_CHANGED, intent.getAction());
diff --git a/tests/telephonytests/src/com/android/internal/telephony/NetworkRegistrationInfoTest.java b/tests/telephonytests/src/com/android/internal/telephony/NetworkRegistrationInfoTest.java
index a275bb8..8dc8ea7 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/NetworkRegistrationInfoTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/NetworkRegistrationInfoTest.java
@@ -18,6 +18,7 @@
 
 import static junit.framework.Assert.assertEquals;
 
+import android.compat.testing.PlatformCompatChangeRule;
 import android.os.Parcel;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.CellIdentityLte;
@@ -27,13 +28,21 @@
 
 import androidx.test.filters.SmallTest;
 
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestRule;
 
 import java.util.Arrays;
 
 /** Unit tests for {@link NetworkRegistrationInfo}. */
 public class NetworkRegistrationInfoTest {
 
+    @Rule
+    public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
     @Test
     @SmallTest
     public void testParcel() {
@@ -87,4 +96,47 @@
         assertEquals(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING,
                 nri.getNetworkRegistrationState());
     }
+
+    @Test
+    @DisableCompatChanges({NetworkRegistrationInfo.RETURN_REGISTRATION_STATE_EMERGENCY})
+    public void testReturnRegistrationStateEmergencyDisabled() {
+        // LTE
+        NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_EMERGENCY)
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .build();
+
+        assertEquals(NetworkRegistrationInfo.REGISTRATION_STATE_DENIED, nri.getRegistrationState());
+
+        // NR
+        nri = new NetworkRegistrationInfo.Builder()
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_EMERGENCY)
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_NR)
+                .build();
+
+        assertEquals(NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING,
+                nri.getRegistrationState());
+    }
+
+    @Test
+    @EnableCompatChanges({NetworkRegistrationInfo.RETURN_REGISTRATION_STATE_EMERGENCY})
+    public void testReturnRegistrationStateEmergencyEnabled() {
+        // LTE
+        NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_EMERGENCY)
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+                .build();
+
+        assertEquals(NetworkRegistrationInfo.REGISTRATION_STATE_EMERGENCY,
+                nri.getRegistrationState());
+
+        // NR
+        nri = new NetworkRegistrationInfo.Builder()
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_EMERGENCY)
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_NR)
+                .build();
+
+        assertEquals(NetworkRegistrationInfo.REGISTRATION_STATE_EMERGENCY,
+                nri.getRegistrationState());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
index 2b487f2..e171a5e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
@@ -20,12 +20,12 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
 import android.content.Context;
-import android.content.Intent;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.IPowerManager;
@@ -50,7 +50,6 @@
 
 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;
@@ -61,21 +60,10 @@
 
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-@Ignore("b/240911460")
 public class NetworkTypeControllerTest extends TelephonyTest {
-    // private constants copied over from NetworkTypeController
-    private static final int EVENT_DATA_RAT_CHANGED = 2;
-    private static final int EVENT_NR_STATE_CHANGED = 3;
-    private static final int EVENT_NR_FREQUENCY_CHANGED = 4;
-    private static final int EVENT_PHYSICAL_LINK_STATUS_CHANGED = 5;
-    private static final int EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED = 6;
-    private static final int EVENT_RADIO_OFF_OR_UNAVAILABLE = 10;
-    private static final int EVENT_PREFERRED_NETWORK_MODE_CHANGED = 11;
-    private static final int EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED = 13;
-
     private NetworkTypeController mNetworkTypeController;
     private PersistableBundle mBundle;
-    private DataNetworkControllerCallback mDataNetworkControllerCallback;
+    private CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener;
 
     private IState getCurrentState() throws Exception {
         Method method = StateMachine.class.getDeclaredMethod("getCurrentState");
@@ -89,11 +77,12 @@
         method.invoke(mNetworkTypeController);
     }
 
-    private void broadcastCarrierConfigs() {
-        Intent intent = new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        intent.putExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, mPhone.getSubId());
-        intent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, mPhone.getPhoneId());
-        mContext.sendBroadcast(intent);
+    private void sendCarrierConfigChanged() {
+        if (mCarrierConfigChangeListener != null) {
+            mCarrierConfigChangeListener.onCarrierConfigChanged(mPhone.getPhoneId(),
+                    mPhone.getSubId(), TelephonyManager.UNKNOWN_CARRIER_ID,
+                    TelephonyManager.UNKNOWN_CARRIER_ID);
+        }
         processAllMessages();
     }
 
@@ -104,9 +93,8 @@
         mBundle.putString(CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING,
                 "connected_mmwave:5G_Plus,connected:5G,not_restricted_rrc_idle:5G,"
                         + "not_restricted_rrc_con:5G");
-        mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_CAPABLE_PCO_ID_INT, 0xFF03);
         mBundle.putInt(CarrierConfigManager.KEY_LTE_PLUS_THRESHOLD_BANDWIDTH_KHZ_INT, 20000);
-        broadcastCarrierConfigs();
+        sendCarrierConfigChanged();
 
         replaceInstance(Handler.class, "mLooper", mDisplayInfoController, Looper.myLooper());
         doReturn(RadioAccessFamily.getRafFromNetworkType(
@@ -115,14 +103,14 @@
         doReturn(true).when(mTelephonyManager).isRadioInterfaceCapabilitySupported(
                 TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
         doReturn(new int[] {0}).when(mServiceState).getCellBandwidths();
+        // 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);
         processAllMessages();
-
-        ArgumentCaptor<DataNetworkControllerCallback> dataNetworkControllerCallbackCaptor =
-                ArgumentCaptor.forClass(DataNetworkControllerCallback.class);
-        verify(mDataNetworkController).registerDataNetworkControllerCallback(
-                dataNetworkControllerCallbackCaptor.capture());
-        mDataNetworkControllerCallback = dataNetworkControllerCallbackCaptor.getValue();
+        verify(mCarrierConfigManager).registerCarrierConfigChangeListener(any(),
+                listenerArgumentCaptor.capture());
+        mCarrierConfigChangeListener = listenerArgumentCaptor.getAllValues().get(0);
     }
 
     @After
@@ -234,7 +222,7 @@
         doReturn("test_patternShowAdvanced").when(mServiceState).getOperatorAlphaLongRaw();
         mBundle.putString(CarrierConfigManager.KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING,
                 ".*_patternShowAdvanced");
-        broadcastCarrierConfigs();
+        sendCarrierConfigChanged();
         updateOverrideNetworkType();
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO,
                 mNetworkTypeController.getOverrideNetworkType());
@@ -259,8 +247,7 @@
     @Test
     public void testTransitionToCurrentStateLegacy() throws Exception {
         assertEquals("DefaultState", getCurrentState().getName());
-
-        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("legacy", getCurrentState().getName());
     }
@@ -269,8 +256,7 @@
     public void testTransitionToCurrentStateRestricted() throws Exception {
         assertEquals("DefaultState", getCurrentState().getName());
         doReturn(NetworkRegistrationInfo.NR_STATE_RESTRICTED).when(mServiceState).getNrState();
-
-        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("restricted", getCurrentState().getName());
     }
@@ -279,9 +265,9 @@
     public void testTransitionToCurrentStateIdle() throws Exception {
         assertEquals("DefaultState", getCurrentState().getName());
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
-        mNetworkTypeController.sendMessage(EVENT_PHYSICAL_LINK_STATUS_CHANGED,
+        mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
                 new AsyncResult(null, DataCallResponse.LINK_STATUS_DORMANT, null));
-        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_idle", getCurrentState().getName());
     }
@@ -296,8 +282,8 @@
 
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
         setPhysicalLinkStatus(false);
-        mNetworkTypeController.sendMessage(EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED);
-        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_idle", getCurrentState().getName());
     }
@@ -309,17 +295,15 @@
         doReturn(true).when(mTelephonyManager).isRadioInterfaceCapabilitySupported(
                 TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
         mNetworkTypeController = new NetworkTypeController(mPhone, mDisplayInfoController);
-        broadcastCarrierConfigs();
+        sendCarrierConfigChanged();
         processAllMessages();
         assertEquals("DefaultState", getCurrentState().getName());
 
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
-        mNetworkTypeController.sendMessage(EVENT_PHYSICAL_LINK_STATUS_CHANGED,
+        mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
                 new AsyncResult(null, DataCallResponse.LINK_STATUS_DORMANT, null));
-        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
-
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
-
         assertEquals("not_restricted_rrc_idle", getCurrentState().getName());
     }
 
@@ -327,9 +311,9 @@
     public void testTransitionToCurrentStateLteConnected() throws Exception {
         assertEquals("DefaultState", getCurrentState().getName());
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
-        mNetworkTypeController.sendMessage(EVENT_PHYSICAL_LINK_STATUS_CHANGED,
+        mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
                 new AsyncResult(null, DataCallResponse.LINK_STATUS_ACTIVE, null));
-        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_con", getCurrentState().getName());
     }
@@ -340,14 +324,14 @@
         doReturn(true).when(mTelephonyManager).isRadioInterfaceCapabilitySupported(
                 TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
         mNetworkTypeController = new NetworkTypeController(mPhone, mDisplayInfoController);
-        broadcastCarrierConfigs();
+        sendCarrierConfigChanged();
         processAllMessages();
         assertEquals("DefaultState", getCurrentState().getName());
 
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
         setPhysicalLinkStatus(true);
-        mNetworkTypeController.sendMessage(EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED);
-        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_con", getCurrentState().getName());
     }
@@ -360,17 +344,15 @@
         doReturn(true).when(mTelephonyManager).isRadioInterfaceCapabilitySupported(
                 TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
         mNetworkTypeController = new NetworkTypeController(mPhone, mDisplayInfoController);
-        broadcastCarrierConfigs();
+        sendCarrierConfigChanged();
         processAllMessages();
         assertEquals("DefaultState", getCurrentState().getName());
 
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
-        mNetworkTypeController.sendMessage(EVENT_PHYSICAL_LINK_STATUS_CHANGED,
+        mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
                 new AsyncResult(null, DataCallResponse.LINK_STATUS_ACTIVE, null));
-        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
-
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
-
         assertEquals("not_restricted_rrc_con", getCurrentState().getName());
     }
 
@@ -379,7 +361,7 @@
         assertEquals("DefaultState", getCurrentState().getName());
         doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
 
-        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("connected", getCurrentState().getName());
     }
@@ -390,7 +372,7 @@
         doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
         doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
 
-        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("connected_mmwave", getCurrentState().getName());
     }
@@ -410,9 +392,10 @@
         List<PhysicalChannelConfig> lastPhysicalChannelConfigList = new ArrayList<>();
         lastPhysicalChannelConfigList.add(physicalChannelConfig);
         doReturn(lastPhysicalChannelConfigList).when(mSST).getPhysicalChannelConfigList();
-        broadcastCarrierConfigs();
+        sendCarrierConfigChanged();
 
-        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("connected_mmwave", getCurrentState().getName());
     }
@@ -432,9 +415,10 @@
         List<PhysicalChannelConfig> lastPhysicalChannelConfigList = new ArrayList<>();
         lastPhysicalChannelConfigList.add(physicalChannelConfig);
         doReturn(lastPhysicalChannelConfigList).when(mSST).getPhysicalChannelConfigList();
-        broadcastCarrierConfigs();
+        sendCarrierConfigChanged();
 
-        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("connected", getCurrentState().getName());
     }
@@ -444,10 +428,9 @@
         assertEquals("DefaultState", getCurrentState().getName());
         doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
         doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
-        mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_CAPABLE_PCO_ID_INT, 0);
-        broadcastCarrierConfigs();
+        sendCarrierConfigChanged();
 
-        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("connected_mmwave", getCurrentState().getName());
     }
@@ -459,9 +442,14 @@
         doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
         doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
         mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_CAPABLE_PCO_ID_INT, 0xFF03);
-        broadcastCarrierConfigs();
-        mDataNetworkControllerCallback.onNrAdvancedCapableByPcoChanged(false);
-        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        sendCarrierConfigChanged();
+
+        ArgumentCaptor<DataNetworkControllerCallback> dataNetworkControllerCallbackCaptor =
+                ArgumentCaptor.forClass(DataNetworkControllerCallback.class);
+        verify(mDataNetworkController).registerDataNetworkControllerCallback(
+                dataNetworkControllerCallbackCaptor.capture());
+        DataNetworkControllerCallback callback = dataNetworkControllerCallbackCaptor.getValue();
+        callback.onNrAdvancedCapableByPcoChanged(false);
         processAllMessages();
         assertEquals("connected", getCurrentState().getName());
     }
@@ -473,10 +461,14 @@
         doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
         doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
         mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_CAPABLE_PCO_ID_INT, 0xFF00);
-        broadcastCarrierConfigs();
+        sendCarrierConfigChanged();
 
-        mDataNetworkControllerCallback.onNrAdvancedCapableByPcoChanged(false);
-        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        ArgumentCaptor<DataNetworkControllerCallback> dataNetworkControllerCallbackCaptor =
+                ArgumentCaptor.forClass(DataNetworkControllerCallback.class);
+        verify(mDataNetworkController).registerDataNetworkControllerCallback(
+                dataNetworkControllerCallbackCaptor.capture());
+        DataNetworkControllerCallback callback = dataNetworkControllerCallbackCaptor.getValue();
+        callback.onNrAdvancedCapableByPcoChanged(false);
         processAllMessages();
         assertEquals("connected", getCurrentState().getName());
     }
@@ -488,11 +480,14 @@
         doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
         doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
         mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_CAPABLE_PCO_ID_INT, 0xFF03);
-        broadcastCarrierConfigs();
+        sendCarrierConfigChanged();
 
-        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
-        processAllMessages();
-        mDataNetworkControllerCallback.onNrAdvancedCapableByPcoChanged(true);
+        ArgumentCaptor<DataNetworkControllerCallback> dataNetworkControllerCallbackCaptor =
+                ArgumentCaptor.forClass(DataNetworkControllerCallback.class);
+        verify(mDataNetworkController).registerDataNetworkControllerCallback(
+                dataNetworkControllerCallbackCaptor.capture());
+        DataNetworkControllerCallback callback = dataNetworkControllerCallbackCaptor.getValue();
+        callback.onNrAdvancedCapableByPcoChanged(true);
         processAllMessages();
         assertEquals("connected_mmwave", getCurrentState().getName());
     }
@@ -502,7 +497,7 @@
         testTransitionToCurrentStateLegacy();
         doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
 
-        mNetworkTypeController.sendMessage(EVENT_DATA_RAT_CHANGED);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("connected", getCurrentState().getName());
     }
@@ -512,7 +507,7 @@
         testTransitionToCurrentStateNrConnected();
         doReturn(NetworkRegistrationInfo.NR_STATE_RESTRICTED).when(mServiceState).getNrState();
 
-        mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("restricted", getCurrentState().getName());
     }
@@ -523,9 +518,8 @@
         testTransitionToCurrentStateNrConnectedMmwave();
         doReturn(ServiceState.FREQUENCY_RANGE_LOW).when(mServiceState).getNrFrequencyRange();
 
-        mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
-
         assertEquals("connected", getCurrentState().getName());
     }
 
@@ -535,9 +529,8 @@
         testTransitionToCurrentStateNrConnected();
         doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
 
-        mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
-
         assertEquals("connected_mmwave", getCurrentState().getName());
     }
 
@@ -545,12 +538,11 @@
     public void testNrPhysicalChannelChangeFromNrConnectedMmwaveToLteConnected() throws Exception {
         testTransitionToCurrentStateNrConnectedMmwave();
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
-        mNetworkTypeController.sendMessage(EVENT_PHYSICAL_LINK_STATUS_CHANGED,
-                new AsyncResult(null, DataCallResponse.LINK_STATUS_ACTIVE, null));
-        mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED);
-        mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED);
-        processAllMessages();
 
+        mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
+                new AsyncResult(null, DataCallResponse.LINK_STATUS_ACTIVE, null));
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
+        processAllMessages();
         assertEquals("not_restricted_rrc_con", getCurrentState().getName());
     }
 
@@ -564,15 +556,13 @@
         testTransitionToCurrentStateNrConnectedMmwave();
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
         setPhysicalLinkStatus(true);
-        mNetworkTypeController.sendMessage(EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED);
-        mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED);
-        mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
 
         processAllMessages();
         assertEquals("not_restricted_rrc_con", getCurrentState().getName());
     }
 
-
     @Test
     public void testUsingUserDataForRrcDetection_FromNrConnectedMmwaveToLteConnected()
             throws Exception {
@@ -581,14 +571,13 @@
         doReturn(true).when(mTelephonyManager).isRadioInterfaceCapabilitySupported(
                 TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
         mNetworkTypeController = new NetworkTypeController(mPhone, mDisplayInfoController);
-        broadcastCarrierConfigs();
+        sendCarrierConfigChanged();
         processAllMessages();
         testTransitionToCurrentStateNrConnectedMmwave();
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
-        mNetworkTypeController.sendMessage(EVENT_PHYSICAL_LINK_STATUS_CHANGED,
+        mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
                 new AsyncResult(null, DataCallResponse.LINK_STATUS_ACTIVE, null));
-        mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED);
-        mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
 
         processAllMessages();
         assertEquals("not_restricted_rrc_con", getCurrentState().getName());
@@ -603,7 +592,8 @@
 
         doReturn(true).when(mServiceState).isUsingCarrierAggregation();
         doReturn(new int[] {30000}).when(mServiceState).getCellBandwidths();
-        mNetworkTypeController.sendMessage(EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA,
                 mNetworkTypeController.getOverrideNetworkType());
@@ -615,13 +605,13 @@
         mBundle = mContextFixture.getCarrierConfigBundle();
         mBundle.putString(CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING,
                 "connected_mmwave:5G_Plus,connected:5G");
-        broadcastCarrierConfigs();
+        sendCarrierConfigChanged();
 
         // Transition to LTE connected state
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
-        mNetworkTypeController.sendMessage(EVENT_PHYSICAL_LINK_STATUS_CHANGED,
+        mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
                 new AsyncResult(null, DataCallResponse.LINK_STATUS_ACTIVE, null));
-        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_con", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
@@ -630,7 +620,8 @@
         // LTE -> LTE+
         doReturn(true).when(mServiceState).isUsingCarrierAggregation();
         doReturn(new int[] {30000}).when(mServiceState).getCellBandwidths();
-        mNetworkTypeController.sendMessage(EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA,
                 mNetworkTypeController.getOverrideNetworkType());
@@ -642,13 +633,13 @@
         mBundle = mContextFixture.getCarrierConfigBundle();
         mBundle.putString(CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING,
                 "connected_mmwave:5G_Plus,connected:5G");
-        broadcastCarrierConfigs();
+        sendCarrierConfigChanged();
 
         // Transition to idle state
         doReturn(NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED).when(mServiceState).getNrState();
-        mNetworkTypeController.sendMessage(EVENT_PHYSICAL_LINK_STATUS_CHANGED,
+        mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
                 new AsyncResult(null, DataCallResponse.LINK_STATUS_DORMANT, null));
-        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_idle", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
@@ -657,7 +648,8 @@
         // LTE -> LTE+
         doReturn(true).when(mServiceState).isUsingCarrierAggregation();
         doReturn(new int[] {30000}).when(mServiceState).getCellBandwidths();
-        mNetworkTypeController.sendMessage(EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA,
                 mNetworkTypeController.getOverrideNetworkType());
@@ -667,7 +659,7 @@
     public void testEventPhysicalLinkStatusChanged() throws Exception {
         testTransitionToCurrentStateLteConnected();
         doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
-        mNetworkTypeController.sendMessage(EVENT_PHYSICAL_LINK_STATUS_CHANGED,
+        mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
                 new AsyncResult(null, DataCallResponse.LINK_STATUS_DORMANT, null));
 
         processAllMessages();
@@ -684,8 +676,8 @@
         testTransitionToCurrentStateLteConnectedSupportPhysicalChannelConfig1_6();
         doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
         setPhysicalLinkStatus(false);
-        mNetworkTypeController.sendMessage(EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED);
-
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED */);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("not_restricted_rrc_idle", getCurrentState().getName());
     }
@@ -698,11 +690,11 @@
         doReturn(true).when(mTelephonyManager).isRadioInterfaceCapabilitySupported(
                 TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
         mNetworkTypeController = new NetworkTypeController(mPhone, mDisplayInfoController);
-        broadcastCarrierConfigs();
+        sendCarrierConfigChanged();
         processAllMessages();
         testTransitionToCurrentStateLteConnected_usingUserDataForRrcDetection();
         doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
-        mNetworkTypeController.sendMessage(EVENT_PHYSICAL_LINK_STATUS_CHANGED,
+        mNetworkTypeController.sendMessage(4 /* EVENT_PHYSICAL_LINK_STATUS_CHANGED */,
                 new AsyncResult(null, DataCallResponse.LINK_STATUS_DORMANT, null));
 
         processAllMessages();
@@ -715,7 +707,7 @@
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
                 mNetworkTypeController.getOverrideNetworkType());
 
-        mNetworkTypeController.sendMessage(EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED,
+        mNetworkTypeController.sendMessage(5 /* EVENT_PHYSICAL_CHANNEL_CONFIG_NOTIF_CHANGED */,
                 new AsyncResult(null, false, null));
         processAllMessages();
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
@@ -729,8 +721,7 @@
                 mNetworkTypeController.getOverrideNetworkType());
 
         doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
-
-        mNetworkTypeController.sendMessage(EVENT_RADIO_OFF_OR_UNAVAILABLE);
+        mNetworkTypeController.sendMessage(9 /* EVENT_RADIO_OFF_OR_UNAVAILABLE */);
         processAllMessages();
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
                 mNetworkTypeController.getOverrideNetworkType());
@@ -747,7 +738,7 @@
                 TelephonyManager.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA)).when(
                 mPhone).getCachedAllowedNetworkTypesBitmask();
 
-        mNetworkTypeController.sendMessage(EVENT_PREFERRED_NETWORK_MODE_CHANGED);
+        mNetworkTypeController.sendMessage(10 /* EVENT_PREFERRED_NETWORK_MODE_CHANGED */);
         processAllMessages();
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
                 mNetworkTypeController.getOverrideNetworkType());
@@ -758,7 +749,7 @@
         doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
         mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
                 "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10");
-        broadcastCarrierConfigs();
+        sendCarrierConfigChanged();
 
         assertEquals("connected", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
@@ -766,13 +757,13 @@
 
         // should trigger 10 second timer
         doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
-        mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
 
         assertEquals("legacy", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
                 mNetworkTypeController.getOverrideNetworkType());
-        assertTrue(mNetworkTypeController.is5GHysteresisActive());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
 
         // timer expires
         moveTimeForward(10 * 1000);
@@ -781,7 +772,7 @@
         assertEquals("legacy", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
                 mNetworkTypeController.getOverrideNetworkType());
-        assertFalse(mNetworkTypeController.is5GHysteresisActive());
+        assertFalse(mNetworkTypeController.areAnyTimersActive());
     }
 
     @Test
@@ -789,14 +780,14 @@
         doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
         mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
                 "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10");
-        broadcastCarrierConfigs();
+        sendCarrierConfigChanged();
 
         IPowerManager powerManager = mock(IPowerManager.class);
         PowerManager pm = new PowerManager(mContext, powerManager, mock(IThermalService.class),
                 new Handler(Looper.myLooper()));
         doReturn(pm).when(mContext).getSystemService(Context.POWER_SERVICE);
         doReturn(true).when(powerManager).isDeviceIdleMode();
-        mNetworkTypeController.sendMessage(17 /*EVENT_DEVICE_IDLE_MODE_CHANGED*/);
+        mNetworkTypeController.sendMessage(12 /* EVENT_DEVICE_IDLE_MODE_CHANGED */);
 
         assertEquals("connected", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
@@ -804,13 +795,13 @@
 
         // should not trigger timer
         doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
-        mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
 
         assertEquals("legacy", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
                 mNetworkTypeController.getOverrideNetworkType());
-        assertFalse(mNetworkTypeController.is5GHysteresisActive());
+        assertFalse(mNetworkTypeController.areAnyTimersActive());
     }
 
     @Test
@@ -818,7 +809,7 @@
         doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
         mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
                 "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10");
-        broadcastCarrierConfigs();
+        sendCarrierConfigChanged();
 
         assertEquals("connected", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
@@ -826,17 +817,17 @@
 
         // trigger 10 second timer after disconnecting from NR
         doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
-        mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
 
         assertEquals("legacy", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
                 mNetworkTypeController.getOverrideNetworkType());
-        assertTrue(mNetworkTypeController.is5GHysteresisActive());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
 
         // reconnect to NR in the middle of the timer
         doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
-        mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
 
         // timer expires
         moveTimeForward(10 * 1000);
@@ -846,7 +837,7 @@
         assertEquals("connected", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
                 mNetworkTypeController.getOverrideNetworkType());
-        assertFalse(mNetworkTypeController.is5GHysteresisActive());
+        assertFalse(mNetworkTypeController.areAnyTimersActive());
     }
 
     @Test
@@ -854,7 +845,7 @@
         doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
         mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
                 "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10");
-        broadcastCarrierConfigs();
+        sendCarrierConfigChanged();
 
         assertEquals("connected", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
@@ -868,14 +859,14 @@
         // trigger 10 second timer after disconnecting from NR, and then it does the timer reset
         // since the network mode without the NR capability.
         doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
-        mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
 
         // timer should be reset.
         assertEquals("legacy", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
                 mNetworkTypeController.getOverrideNetworkType());
-        assertFalse(mNetworkTypeController.is5GHysteresisActive());
+        assertFalse(mNetworkTypeController.areAnyTimersActive());
     }
 
     @Test
@@ -884,7 +875,7 @@
         doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
         mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
                 "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10");
-        broadcastCarrierConfigs();
+        sendCarrierConfigChanged();
 
         assertEquals("connected_mmwave", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
@@ -892,13 +883,13 @@
 
         // should trigger 10 second timer
         doReturn(ServiceState.FREQUENCY_RANGE_LOW).when(mServiceState).getNrFrequencyRange();
-        mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
 
         assertEquals("connected", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
                 mNetworkTypeController.getOverrideNetworkType());
-        assertTrue(mNetworkTypeController.is5GHysteresisActive());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
 
         // timer expires
         moveTimeForward(10 * 1000);
@@ -907,7 +898,7 @@
         assertEquals("connected", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
                 mNetworkTypeController.getOverrideNetworkType());
-        assertFalse(mNetworkTypeController.is5GHysteresisActive());
+        assertFalse(mNetworkTypeController.areAnyTimersActive());
     }
 
     @Test
@@ -916,7 +907,7 @@
         doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
         mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
                 "connected_mmwave,any,10;connected,any,10;not_restricted_rrc_con,any,10");
-        broadcastCarrierConfigs();
+        sendCarrierConfigChanged();
 
         assertEquals("connected_mmwave", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
@@ -924,17 +915,17 @@
 
         // trigger 10 second timer after disconnecting from NR
         doReturn(ServiceState.FREQUENCY_RANGE_LOW).when(mServiceState).getNrFrequencyRange();
-        mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
 
         assertEquals("connected", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
                 mNetworkTypeController.getOverrideNetworkType());
-        assertTrue(mNetworkTypeController.is5GHysteresisActive());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
 
         // reconnect to NR in the middle of the timer
         doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
-        mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
 
         // timer expires
         moveTimeForward(10 * 1000);
@@ -944,7 +935,7 @@
         assertEquals("connected_mmwave", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
                 mNetworkTypeController.getOverrideNetworkType());
-        assertFalse(mNetworkTypeController.is5GHysteresisActive());
+        assertFalse(mNetworkTypeController.areAnyTimersActive());
     }
 
     @Test
@@ -954,7 +945,7 @@
                 "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,any,30");
-        broadcastCarrierConfigs();
+        sendCarrierConfigChanged();
 
         assertEquals("connected", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
@@ -962,13 +953,13 @@
 
         // should trigger 10 second primary timer
         doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
-        mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
 
         assertEquals("legacy", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
                 mNetworkTypeController.getOverrideNetworkType());
-        assertTrue(mNetworkTypeController.is5GHysteresisActive());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
 
         // primary timer expires
         moveTimeForward(10 * 1000);
@@ -978,7 +969,7 @@
         assertEquals("legacy", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
                 mNetworkTypeController.getOverrideNetworkType());
-        assertTrue(mNetworkTypeController.is5GHysteresisActive());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
 
         // secondary timer expires
         moveTimeForward(30 * 1000);
@@ -987,7 +978,7 @@
         assertEquals("legacy", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
                 mNetworkTypeController.getOverrideNetworkType());
-        assertFalse(mNetworkTypeController.is5GHysteresisActive());
+        assertFalse(mNetworkTypeController.areAnyTimersActive());
     }
 
     @Test
@@ -997,7 +988,7 @@
                 "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,any,30");
-        broadcastCarrierConfigs();
+        sendCarrierConfigChanged();
 
         assertEquals("connected", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
@@ -1005,13 +996,13 @@
 
         // should trigger 10 second primary timer
         doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
-        mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
 
         assertEquals("legacy", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
                 mNetworkTypeController.getOverrideNetworkType());
-        assertTrue(mNetworkTypeController.is5GHysteresisActive());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
 
         // primary timer expires
         moveTimeForward(10 * 1000);
@@ -1021,11 +1012,11 @@
         assertEquals("legacy", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
                 mNetworkTypeController.getOverrideNetworkType());
-        assertTrue(mNetworkTypeController.is5GHysteresisActive());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
 
         // reconnect to NR in the middle of the timer
         doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
-        mNetworkTypeController.sendMessage(EVENT_NR_STATE_CHANGED);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
 
         // secondary timer expires
         moveTimeForward(30 * 1000);
@@ -1035,7 +1026,7 @@
         assertEquals("connected", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
                 mNetworkTypeController.getOverrideNetworkType());
-        assertFalse(mNetworkTypeController.is5GHysteresisActive());
+        assertFalse(mNetworkTypeController.areAnyTimersActive());
     }
 
     @Test
@@ -1046,7 +1037,7 @@
                 "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");
-        broadcastCarrierConfigs();
+        sendCarrierConfigChanged();
 
         assertEquals("connected_mmwave", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
@@ -1054,13 +1045,13 @@
 
         // should trigger 10 second primary timer
         doReturn(ServiceState.FREQUENCY_RANGE_LOW).when(mServiceState).getNrFrequencyRange();
-        mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
 
         assertEquals("connected", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
                 mNetworkTypeController.getOverrideNetworkType());
-        assertTrue(mNetworkTypeController.is5GHysteresisActive());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
 
         // primary timer expires
         moveTimeForward(10 * 1000);
@@ -1070,7 +1061,7 @@
         assertEquals("connected", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
                 mNetworkTypeController.getOverrideNetworkType());
-        assertTrue(mNetworkTypeController.is5GHysteresisActive());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
 
         // secondary timer expires
         moveTimeForward(30 * 1000);
@@ -1079,7 +1070,7 @@
         assertEquals("connected", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
                 mNetworkTypeController.getOverrideNetworkType());
-        assertFalse(mNetworkTypeController.is5GHysteresisActive());
+        assertFalse(mNetworkTypeController.areAnyTimersActive());
     }
 
     @Test
@@ -1090,7 +1081,7 @@
                 "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");
-        broadcastCarrierConfigs();
+        sendCarrierConfigChanged();
 
         assertEquals("connected_mmwave", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
@@ -1098,13 +1089,13 @@
 
         // should trigger 10 second primary timer
         doReturn(ServiceState.FREQUENCY_RANGE_LOW).when(mServiceState).getNrFrequencyRange();
-        mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
 
         assertEquals("connected", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
                 mNetworkTypeController.getOverrideNetworkType());
-        assertTrue(mNetworkTypeController.is5GHysteresisActive());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
 
         // primary timer expires
         moveTimeForward(10 * 1000);
@@ -1114,11 +1105,11 @@
         assertEquals("connected", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
                 mNetworkTypeController.getOverrideNetworkType());
-        assertTrue(mNetworkTypeController.is5GHysteresisActive());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
 
         // reconnect to NR in the middle of the timer
         doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
-        mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
 
         // secondary timer expires
         moveTimeForward(30 * 1000);
@@ -1128,7 +1119,7 @@
         assertEquals("connected_mmwave", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
                 mNetworkTypeController.getOverrideNetworkType());
-        assertFalse(mNetworkTypeController.is5GHysteresisActive());
+        assertFalse(mNetworkTypeController.areAnyTimersActive());
     }
 
     @Test
@@ -1139,7 +1130,7 @@
                 "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");
-        broadcastCarrierConfigs();
+        sendCarrierConfigChanged();
 
         assertEquals("connected_mmwave", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
@@ -1147,13 +1138,13 @@
 
         // should trigger 10 second primary timer
         doReturn(ServiceState.FREQUENCY_RANGE_LOW).when(mServiceState).getNrFrequencyRange();
-        mNetworkTypeController.sendMessage(EVENT_NR_FREQUENCY_CHANGED);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
 
         assertEquals("connected", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
                 mNetworkTypeController.getOverrideNetworkType());
-        assertTrue(mNetworkTypeController.is5GHysteresisActive());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
 
         // rat is UMTS, should stop timer
         NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
@@ -1162,13 +1153,89 @@
                 .build();
         doReturn(nri).when(mServiceState).getNetworkRegistrationInfo(anyInt(), anyInt());
         doReturn(NetworkRegistrationInfo.NR_STATE_NONE).when(mServiceState).getNrState();
-        mNetworkTypeController.sendMessage(EVENT_DATA_RAT_CHANGED);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
 
         assertEquals("legacy", getCurrentState().getName());
         assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
                 mNetworkTypeController.getOverrideNetworkType());
-        assertFalse(mNetworkTypeController.is5GHysteresisActive());
+        assertFalse(mNetworkTypeController.areAnyTimersActive());
+    }
+
+    @Test
+    public void testNrTimerResetWhenConnected() throws Exception {
+        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();
+
+        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));
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
+        processAllMessages();
+
+        assertEquals("not_restricted_rrc_con", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // 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_NSA,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // rat is NR, should stop timer
+        NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_NR)
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+                .build();
+        doReturn(nri).when(mServiceState).getNetworkRegistrationInfo(anyInt(), anyInt());
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
+        processAllMessages();
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertFalse(mNetworkTypeController.areAnyTimersActive());
+    }
+
+    @Test
+    public void testNrTimerResetWhenConnectedAdvanced() throws Exception {
+        testTransitionToCurrentStateNrConnectedMmwave();
+        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());
+
+        // not advanced, should not stop timer
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
+        processAllMessages();
+
+        assertEquals("connected", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
     }
 
     private void setPhysicalLinkStatus(boolean state) {
@@ -1195,9 +1262,9 @@
         doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
         doReturn(new int[] {19999}).when(mServiceState).getCellBandwidths();
         mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 20000);
-        broadcastCarrierConfigs();
+        sendCarrierConfigChanged();
 
-        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("connected", getCurrentState().getName());
     }
@@ -1207,14 +1274,9 @@
         assertEquals("DefaultState", getCurrentState().getName());
         doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
         doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
-        List<PhysicalChannelConfig> lastPhysicalChannelConfigList = new ArrayList<>();
-        lastPhysicalChannelConfigList.add(new PhysicalChannelConfig.Builder()
-                .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
-                .setCellBandwidthDownlinkKhz(20001)
-                .build());
-        doReturn(lastPhysicalChannelConfigList).when(mSST).getPhysicalChannelConfigList();
+        doReturn(new int[] {20001}).when(mServiceState).getCellBandwidths();
         mBundle.putInt(CarrierConfigManager.KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 20000);
-        broadcastCarrierConfigs();
+        sendCarrierConfigChanged();
 
         mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
         processAllMessages();
@@ -1241,7 +1303,7 @@
         mBundle.putBoolean(
                 CarrierConfigManager.KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL,
                 true);
-        broadcastCarrierConfigs();
+        sendCarrierConfigChanged();
 
         mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
         processAllMessages();
@@ -1255,9 +1317,9 @@
         doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
         doReturn(ServiceState.FREQUENCY_RANGE_MMWAVE).when(mServiceState).getNrFrequencyRange();
         mBundle.putBoolean(CarrierConfigManager.KEY_ENABLE_NR_ADVANCED_WHILE_ROAMING_BOOL, false);
-        broadcastCarrierConfigs();
+        sendCarrierConfigChanged();
 
-        mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+        mNetworkTypeController.sendMessage(3 /* EVENT_SERVICE_STATE_CHANGED */);
         processAllMessages();
         assertEquals("connected", getCurrentState().getName());
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java
index bac1adb..700a246 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java
@@ -138,6 +138,27 @@
 
     @Test
     @SmallTest
+    public void testConfigureAndGetMaxActiveVoiceSubscriptions() throws Exception {
+        init(2);
+        assertEquals(1, mPcm.getStaticPhoneCapability().getMaxActiveVoiceSubscriptions());
+
+        PhoneCapability dualActiveVoiceSubCapability = new PhoneCapability.Builder(
+                PhoneCapability.DEFAULT_DSDS_CAPABILITY)
+                .setMaxActiveVoiceSubscriptions(2)
+                .build();
+
+        ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
+        verify(mMockRadioConfig).getPhoneCapability(captor.capture());
+        Message msg = captor.getValue();
+        AsyncResult.forMessage(msg, dualActiveVoiceSubCapability, null);
+        msg.sendToTarget();
+        processAllMessages();
+
+        assertEquals(2, mPcm.getStaticPhoneCapability().getMaxActiveVoiceSubscriptions());
+    }
+
+    @Test
+    @SmallTest
     public void testSwitchMultiSimConfig_notDsdsCapable_shouldFail() throws Exception {
         init(1);
         assertEquals(PhoneCapability.DEFAULT_SSSS_CAPABILITY, mPcm.getStaticPhoneCapability());
@@ -199,17 +220,9 @@
 
         // Verify clearSubInfoRecord() and onSlotActiveStatusChange() are called for second phone,
         // and not for the first one
-        if (isSubscriptionManagerServiceEnabled()) {
-            verify(mSubscriptionManagerService).markSubscriptionsInactive(1);
-        } else {
-            verify(mSubscriptionController).clearSubInfoRecord(1);
-        }
+        verify(mSubscriptionManagerService).markSubscriptionsInactive(1);
         verify(mMockCi1).onSlotActiveStatusChange(anyBoolean());
-        if (isSubscriptionManagerServiceEnabled()) {
-            verify(mSubscriptionManagerService, never()).markSubscriptionsInactive(0);
-        } else {
-            verify(mSubscriptionController, never()).clearSubInfoRecord(0);
-        }
+        verify(mSubscriptionManagerService, never()).markSubscriptionsInactive(0);
         verify(mMockCi0, never()).onSlotActiveStatusChange(anyBoolean());
 
         // Verify onPhoneRemoved() gets called on MultiSimSettingController phone
@@ -240,12 +253,6 @@
         // setup mocks for  VOICE mSubscriptionManagerService. getter/setter
         doAnswer(invocation -> {
             Integer value = (Integer) invocation.getArguments()[0];
-            Mockito.when(mSubscriptionController.getDefaultVoiceSubId()).thenReturn(value);
-            return null;
-        }).when(mSubscriptionController).setDefaultVoiceSubId(anyInt());
-
-        doAnswer(invocation -> {
-            Integer value = (Integer) invocation.getArguments()[0];
             Mockito.when(mSubscriptionManagerService.getDefaultVoiceSubId()).thenReturn(value);
             return null;
         }).when(mSubscriptionManagerService).setDefaultVoiceSubId(anyInt());
@@ -254,30 +261,17 @@
         // start off the phone stat with 1 active sim. reset values for new test.
         init(1);
 
-        if (isSubscriptionManagerServiceEnabled()) {
-            mSubscriptionManagerService.setDefaultVoiceSubId(startingDefaultSubscriptionId);
-            assertEquals(startingDefaultSubscriptionId,
-                    mSubscriptionManagerService.getDefaultVoiceSubId());
-        } else {
-            mSubscriptionController.setDefaultVoiceSubId(startingDefaultSubscriptionId);
-            assertEquals(startingDefaultSubscriptionId,
-                    mSubscriptionController.getDefaultVoiceSubId());
-        }
+        mSubscriptionManagerService.setDefaultVoiceSubId(startingDefaultSubscriptionId);
+        assertEquals(startingDefaultSubscriptionId,
+                mSubscriptionManagerService.getDefaultVoiceSubId());
 
         // Perform the switch to DSDS mode and ensure all existing checks are not altered
         testSwitchFromSingleToDualSimModeNoReboot();
 
         // VOICE check
-        if (isSubscriptionManagerServiceEnabled()) {
-            assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID /* No CALL Preference value */,
-                    mSubscriptionManagerService.getDefaultVoiceSubId());
-            // Now, when the user goes to place a CALL, they will be prompted on which sim to use.
-        } else {
-            assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID /* No CALL Preference value */,
-                    mSubscriptionController.getDefaultVoiceSubId());
-            // Now, when the user goes to place a CALL, they will be prompted on which sim to use.
-        }
-
+        assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID /* No CALL Preference value */,
+                mSubscriptionManagerService.getDefaultVoiceSubId());
+        // Now, when the user goes to place a CALL, they will be prompted on which sim to use.
     }
 
     /**
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneFactoryTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneFactoryTest.java
deleted file mode 100644
index d1aea03..0000000
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneFactoryTest.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2016 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.fail;
-
-import android.test.suitebuilder.annotation.SmallTest;
-
-import org.junit.Test;
-
-public class PhoneFactoryTest {
-    @Test
-    @SmallTest
-    public void testBeforeMakePhone() {
-        try {
-            PhoneFactory.getDefaultPhone();
-            fail("Expecting IllegalStateException");
-        } catch (IllegalStateException e) {
-        }
-
-        try {
-            PhoneFactory.getPhone(0);
-            fail("Expecting IllegalStateException");
-        } catch (IllegalStateException e) {
-        }
-
-        try {
-            PhoneFactory.getPhones();
-            fail("Expecting IllegalStateException");
-        } catch (IllegalStateException e) {
-        }
-
-        try {
-            PhoneFactory.getNetworkFactory(0);
-            fail("Expecting IllegalStateException");
-        } catch (IllegalStateException e) {
-        }
-    }
-
-    //todo: add test for makeDefaultPhone(). will need some refactoring in PhoneFactory.
-}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
index b7d2913..2f3bbf7 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
@@ -562,6 +562,17 @@
         assertEquals("800-GOOG-114", PhoneNumberUtils.formatNumber("800-GOOG-114", "US"));
     }
 
+    @Test
+    public void testFormatNumber_lowerCase() {
+        assertEquals("(650) 291-0000", PhoneNumberUtils.formatNumber("650 2910000", "us"));
+        assertEquals("223-4567", PhoneNumberUtils.formatNumber("2234567", "us"));
+        assertEquals("011 86 10 8888 0000",
+                PhoneNumberUtils.formatNumber("011861088880000", "us"));
+        assertEquals("010 8888 0000", PhoneNumberUtils.formatNumber("01088880000", "cn"));
+        // formatNumber doesn't format alpha numbers, but keep them as they are.
+        assertEquals("800-GOOG-114", PhoneNumberUtils.formatNumber("800-GOOG-114", "us"));
+    }
+
     /**
      * Tests ability to format phone numbers from Japan using the international format when the
      * current country is not Japan.
@@ -570,6 +581,9 @@
     @Test
     public void testFormatJapanInternational() {
         assertEquals("+81 90-6657-1180", PhoneNumberUtils.formatNumber("+819066571180", "US"));
+
+        // Again with lower case country code
+        assertEquals("+81 90-6657-1180", PhoneNumberUtils.formatNumber("+819066571180", "us"));
     }
 
     /**
@@ -582,8 +596,15 @@
         assertEquals("090-6657-0660", PhoneNumberUtils.formatNumber("09066570660", "JP"));
         assertEquals("090-6657-1180", PhoneNumberUtils.formatNumber("+819066571180", "JP"));
 
+        // Again with lower case country code
+        assertEquals("090-6657-0660", PhoneNumberUtils.formatNumber("09066570660", "jp"));
+        assertEquals("090-6657-1180", PhoneNumberUtils.formatNumber("+819066571180", "jp"));
+
+
         // US number should still be internationally formatted
         assertEquals("+1 650-555-1212", PhoneNumberUtils.formatNumber("+16505551212", "JP"));
+        // Again with lower case country code
+        assertEquals("+1 650-555-1212", PhoneNumberUtils.formatNumber("+16505551212", "jp"));
     }
 
     @SmallTest
@@ -600,6 +621,20 @@
         assertEquals("**650 2910000", PhoneNumberUtils.formatNumber("**650 2910000", "US"));
     }
 
+    // Same as testFormatNumber_LeadingStarAndHash but using lower case country code.
+    @Test
+    public void testFormatNumber_LeadingStarAndHash_countryCodeLowerCase() {
+        // Numbers with a leading '*' or '#' should be left unchanged.
+        assertEquals("*650 2910000", PhoneNumberUtils.formatNumber("*650 2910000", "us"));
+        assertEquals("#650 2910000", PhoneNumberUtils.formatNumber("#650 2910000", "us"));
+        assertEquals("*#650 2910000", PhoneNumberUtils.formatNumber("*#650 2910000", "us"));
+        assertEquals("#*650 2910000", PhoneNumberUtils.formatNumber("#*650 2910000", "us"));
+        assertEquals("#650*2910000", PhoneNumberUtils.formatNumber("#650*2910000", "us"));
+        assertEquals("#650*2910000", PhoneNumberUtils.formatNumber("#650*2910000", "us"));
+        assertEquals("##650 2910000", PhoneNumberUtils.formatNumber("##650 2910000", "us"));
+        assertEquals("**650 2910000", PhoneNumberUtils.formatNumber("**650 2910000", "us"));
+    }
+
     @SmallTest
     @Test
     public void testNormalizeNumber() {
@@ -641,59 +676,63 @@
                 PhoneNumberUtils.formatNumber("011861088880000", "", "GB"));
     }
 
+    // Same as testFormatDailabeNumber but using lower case country code.
+    @Test
+    public void testFormatDailabeNumber_countryCodeLowerCase() {
+        // Using the phoneNumberE164's country code
+        assertEquals("(650) 291-0000",
+                PhoneNumberUtils.formatNumber("6502910000", "+16502910000", "cn"));
+        // Using the default country code for a phone number containing the IDD
+        assertEquals("011 86 10 8888 0000",
+                PhoneNumberUtils.formatNumber("011861088880000", "+861088880000", "us"));
+        assertEquals("00 86 10 8888 0000",
+                PhoneNumberUtils.formatNumber("00861088880000", "+861088880000", "gb"));
+        assertEquals("+86 10 8888 0000",
+                PhoneNumberUtils.formatNumber("+861088880000", "+861088880000", "gb"));
+        // Wrong default country, so no formatting is done
+        assertEquals("011861088880000",
+                PhoneNumberUtils.formatNumber("011861088880000", "+861088880000", "gb"));
+        // The phoneNumberE164 is null
+        assertEquals("(650) 291-0000", PhoneNumberUtils.formatNumber("6502910000", null, "us"));
+        // The given number has a country code.
+        assertEquals("+1 650-291-0000", PhoneNumberUtils.formatNumber("+16502910000", null, "cn"));
+        // The given number was formatted.
+        assertEquals("650-291-0000", PhoneNumberUtils.formatNumber("650-291-0000", null, "us"));
+        // A valid Polish number should be formatted.
+        assertEquals("506 128 687", PhoneNumberUtils.formatNumber("506128687", null, "pl"));
+        // An invalid Polish number should be left as it is. Note Poland doesn't use '0' as a
+        // national prefix; therefore, the leading '0' makes the number invalid.
+        assertEquals("0506128687", PhoneNumberUtils.formatNumber("0506128687", null, "pl"));
+        // Wrong default country, so no formatting is done
+        assertEquals("011861088880000",
+                PhoneNumberUtils.formatNumber("011861088880000", "", "gb"));
+    }
+
     @FlakyTest
     @Test
     @Ignore
     public void testIsEmergencyNumber() {
-        // There are two parallel sets of tests here: one for the
-        // regular isEmergencyNumber() method, and the other for
-        // isPotentialEmergencyNumber().
-        //
         // (The difference is that isEmergencyNumber() will return true
         // only if the specified number exactly matches an actual
-        // emergency number, but isPotentialEmergencyNumber() will
-        // return true if the specified number simply starts with the
-        // same digits as any actual emergency number.)
+        // emergency number
 
         // Tests for isEmergencyNumber():
-        assertTrue(PhoneNumberUtils.isEmergencyNumber("911", "US"));
-        assertTrue(PhoneNumberUtils.isEmergencyNumber("112", "US"));
+        assertTrue(PhoneNumberUtils.isEmergencyNumber("911"));
+        assertTrue(PhoneNumberUtils.isEmergencyNumber("112"));
         // The next two numbers are not valid phone numbers in the US,
         // so do not count as emergency numbers (but they *are* "potential"
         // emergency numbers; see below.)
-        assertFalse(PhoneNumberUtils.isEmergencyNumber("91112345", "US"));
-        assertFalse(PhoneNumberUtils.isEmergencyNumber("11212345", "US"));
+        assertFalse(PhoneNumberUtils.isEmergencyNumber("91112345"));
+        assertFalse(PhoneNumberUtils.isEmergencyNumber("11212345"));
         // A valid mobile phone number from Singapore shouldn't be classified as an emergency number
         // in Singapore, as 911 is not an emergency number there.
-        assertFalse(PhoneNumberUtils.isEmergencyNumber("91121234", "SG"));
+        assertFalse(PhoneNumberUtils.isEmergencyNumber("91121234"));
         // A valid fixed-line phone number from Brazil shouldn't be classified as an emergency number
         // in Brazil, as 112 is not an emergency number there.
-        assertFalse(PhoneNumberUtils.isEmergencyNumber("1121234567", "BR"));
+        assertFalse(PhoneNumberUtils.isEmergencyNumber("1121234567"));
         // A valid local phone number from Brazil shouldn't be classified as an emergency number in
         // Brazil.
-        assertFalse(PhoneNumberUtils.isEmergencyNumber("91112345", "BR"));
-
-        // Tests for isPotentialEmergencyNumber():
-        // These first two are obviously emergency numbers:
-        assertTrue(PhoneNumberUtils.isPotentialEmergencyNumber("911", "US"));
-        assertTrue(PhoneNumberUtils.isPotentialEmergencyNumber("112", "US"));
-        // The next two numbers are not valid phone numbers in the US, but can be used to trick the
-        // system to dial 911 and 112, which are emergency numbers in the US. For the purpose of
-        // addressing that, they are also classified as "potential" emergency numbers in the US.
-        assertTrue(PhoneNumberUtils.isPotentialEmergencyNumber("91112345", "US"));
-        assertTrue(PhoneNumberUtils.isPotentialEmergencyNumber("11212345", "US"));
-
-        // A valid mobile phone number from Singapore shouldn't be classified as an emergency number
-        // in Singapore, as 911 is not an emergency number there.
-        // This test fails on devices that have ecclist property preloaded with 911.
-        // assertFalse(PhoneNumberUtils.isPotentialEmergencyNumber("91121234", "SG"));
-
-        // A valid fixed-line phone number from Brazil shouldn't be classified as an emergency number
-        // in Brazil, as 112 is not an emergency number there.
-        assertFalse(PhoneNumberUtils.isPotentialEmergencyNumber("1121234567", "BR"));
-        // A valid local phone number from Brazil shouldn't be classified as an emergency number in
-        // Brazil.
-        assertFalse(PhoneNumberUtils.isPotentialEmergencyNumber("91112345", "BR"));
+        assertFalse(PhoneNumberUtils.isEmergencyNumber("91112345"));
     }
 
     @SmallTest
@@ -768,6 +807,22 @@
         assertFalse(PhoneNumberUtils.isInternationalNumber("011-613-966-94916", "AU"));
     }
 
+    // Same as testIsInternational but using lower case country code.
+    @Test
+    public void testIsInternational_countryCodeLowerCase() {
+        assertFalse(PhoneNumberUtils.isInternationalNumber("", "us"));
+        assertFalse(PhoneNumberUtils.isInternationalNumber(null, "us"));
+        assertFalse(PhoneNumberUtils.isInternationalNumber("+16505551212", "us"));
+        assertTrue(PhoneNumberUtils.isInternationalNumber("+16505551212", "uk"));
+        assertTrue(PhoneNumberUtils.isInternationalNumber("+16505551212", "jp"));
+        assertTrue(PhoneNumberUtils.isInternationalNumber("+86 10 8888 0000", "us"));
+        assertTrue(PhoneNumberUtils.isInternationalNumber("001-541-754-3010", "de"));
+        assertFalse(PhoneNumberUtils.isInternationalNumber("001-541-754-3010", "us"));
+        assertTrue(PhoneNumberUtils.isInternationalNumber("01161396694916", "us"));
+        assertTrue(PhoneNumberUtils.isInternationalNumber("011-613-966-94916", "us"));
+        assertFalse(PhoneNumberUtils.isInternationalNumber("011-613-966-94916", "au"));
+    }
+
     @SmallTest
     @Test
     public void testIsUriNumber() {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java
index b8fc609..00634a0 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java
@@ -16,13 +16,17 @@
 package com.android.internal.telephony;
 
 import static android.Manifest.permission.READ_PHONE_STATE;
+import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
+import static android.telephony.TelephonyManager.APPTYPE_ISIM;
+import static android.telephony.TelephonyManager.APPTYPE_USIM;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.anyInt;
-import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
@@ -31,17 +35,32 @@
 import android.app.PropertyInvalidatedCache;
 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.test.suitebuilder.annotation.SmallTest;
 
+import com.android.internal.telephony.uicc.IsimUiccRecords;
+import com.android.internal.telephony.uicc.SIMRecords;
+import com.android.internal.telephony.uicc.UiccCardApplication;
+import com.android.internal.telephony.uicc.UiccPort;
+import com.android.internal.telephony.uicc.UiccProfile;
+
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.util.List;
 
 public class PhoneSubInfoControllerTest extends TelephonyTest {
     private static final String FEATURE_ID = "myfeatureId";
+    private static final String PSI_SMSC_TEL1 = "tel:+91123456789";
+    private static final String PSI_SMSC_SIP1 = "sip:+1234567890@abc.pc.operetor1.com;user=phone";
+    private static final String PSI_SMSC_TEL2 = "tel:+91987654321";
+    private static final String PSI_SMSC_SIP2 = "sip:+19876543210@dcf.pc.operetor2.com;user=phone";
 
     private PhoneSubInfoController mPhoneSubInfoControllerUT;
     private AppOpsManager mAppOsMgr;
@@ -57,22 +76,15 @@
         PropertyInvalidatedCache.disableForTestMode();
         /* mPhone -> PhoneId: 0 -> SubId:0
            mSecondPhone -> PhoneId:1 -> SubId: 1*/
-        doReturn(0).when(mSubscriptionController).getPhoneId(eq(0));
         doReturn(0).when(mSubscriptionManagerService).getPhoneId(eq(0));
-        doReturn(1).when(mSubscriptionController).getPhoneId(eq(1));
         doReturn(1).when(mSubscriptionManagerService).getPhoneId(eq(1));
         doReturn(2).when(mTelephonyManager).getPhoneCount();
         doReturn(2).when(mTelephonyManager).getActiveModemCount();
-        doReturn(true).when(mSubscriptionController).isActiveSubId(0, TAG, FEATURE_ID);
         doReturn(true).when(mSubscriptionManagerService).isActiveSubId(0, TAG, FEATURE_ID);
-        doReturn(true).when(mSubscriptionController).isActiveSubId(1, TAG, FEATURE_ID);
         doReturn(true).when(mSubscriptionManagerService).isActiveSubId(1, TAG, FEATURE_ID);
         doReturn(new int[]{0, 1}).when(mSubscriptionManager)
                 .getCompleteActiveSubscriptionIdList();
 
-        mServiceManagerMockedServices.put("isub", mSubscriptionController);
-        doReturn(mSubscriptionController).when(mSubscriptionController)
-                .queryLocalInterface(anyString());
         doReturn(mContext).when(mSecondPhone).getContext();
 
         mAppOsMgr = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
@@ -926,4 +938,357 @@
         assertEquals("VM_SIM_1", mPhoneSubInfoControllerUT
                 .getVoiceMailAlphaTagForSubscriber(1, TAG, FEATURE_ID));
     }
-}
+
+    private void setUpInitials() {
+        UiccPort uiccPort1 = Mockito.mock(UiccPort.class);
+        UiccProfile uiccProfile1 = Mockito.mock(UiccProfile.class);
+        UiccCardApplication uiccCardApplication1 = Mockito.mock(UiccCardApplication.class);
+        SIMRecords simRecords1 = Mockito.mock(SIMRecords.class);
+        IsimUiccRecords isimUiccRecords1 = Mockito.mock(IsimUiccRecords.class);
+
+        doReturn(uiccPort1).when(mPhone).getUiccPort();
+        doReturn(uiccProfile1).when(uiccPort1).getUiccProfile();
+        doReturn(uiccCardApplication1).when(uiccProfile1).getApplicationByType(anyInt());
+        doReturn(simRecords1).when(uiccCardApplication1).getIccRecords();
+        doReturn(isimUiccRecords1).when(uiccCardApplication1).getIccRecords();
+        doReturn(PSI_SMSC_TEL1).when(simRecords1).getSmscIdentity();
+        doReturn(PSI_SMSC_TEL1).when(isimUiccRecords1).getSmscIdentity();
+
+        doReturn(mUiccPort).when(mSecondPhone).getUiccPort();
+        doReturn(mUiccProfile).when(mUiccPort).getUiccProfile();
+        doReturn(mUiccCardApplicationIms).when(mUiccProfile).getApplicationByType(anyInt());
+        doReturn(mSimRecords).when(mUiccCardApplicationIms).getIccRecords();
+        doReturn(mIsimUiccRecords).when(mUiccCardApplicationIms).getIccRecords();
+        doReturn(PSI_SMSC_TEL2).when(mSimRecords).getSmscIdentity();
+        doReturn(PSI_SMSC_TEL2).when(mIsimUiccRecords).getSmscIdentity();
+    }
+
+    @Test
+    public void testGetSmscIdentityForTelUri() {
+        try {
+            setUpInitials();
+            assertEquals(PSI_SMSC_TEL1, mPhoneSubInfoControllerUT
+                    .getSmscIdentity(0, APPTYPE_ISIM).toString());
+            assertEquals(PSI_SMSC_TEL1, mPhoneSubInfoControllerUT
+                    .getSmscIdentity(0, APPTYPE_USIM).toString());
+            assertEquals(PSI_SMSC_TEL2, mPhoneSubInfoControllerUT
+                    .getSmscIdentity(1, APPTYPE_ISIM).toString());
+            assertEquals(PSI_SMSC_TEL2, mPhoneSubInfoControllerUT
+                    .getSmscIdentity(1, APPTYPE_USIM).toString());
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Test
+    public void testGetSmscIdentityForSipUri() {
+        try {
+            UiccPort uiccPort1 = Mockito.mock(UiccPort.class);
+            UiccProfile uiccProfile1 = Mockito.mock(UiccProfile.class);
+            UiccCardApplication uiccCardApplication1 = Mockito.mock(UiccCardApplication.class);
+            SIMRecords simRecords1 = Mockito.mock(SIMRecords.class);
+            IsimUiccRecords isimUiccRecords1 = Mockito.mock(IsimUiccRecords.class);
+
+            doReturn(uiccPort1).when(mPhone).getUiccPort();
+            doReturn(uiccProfile1).when(uiccPort1).getUiccProfile();
+            doReturn(uiccCardApplication1).when(uiccProfile1).getApplicationByType(anyInt());
+            doReturn(simRecords1).when(uiccCardApplication1).getIccRecords();
+            doReturn(isimUiccRecords1).when(uiccCardApplication1).getIccRecords();
+            doReturn(PSI_SMSC_SIP1).when(simRecords1).getSmscIdentity();
+            doReturn(PSI_SMSC_SIP1).when(isimUiccRecords1).getSmscIdentity();
+
+            doReturn(mUiccPort).when(mSecondPhone).getUiccPort();
+            doReturn(mUiccProfile).when(mUiccPort).getUiccProfile();
+            doReturn(mUiccCardApplicationIms).when(mUiccProfile).getApplicationByType(anyInt());
+            doReturn(mSimRecords).when(mUiccCardApplicationIms).getIccRecords();
+            doReturn(mIsimUiccRecords).when(mUiccCardApplicationIms).getIccRecords();
+            doReturn(PSI_SMSC_SIP2).when(mSimRecords).getSmscIdentity();
+            doReturn(PSI_SMSC_SIP2).when(mIsimUiccRecords).getSmscIdentity();
+
+            assertEquals(PSI_SMSC_SIP1, mPhoneSubInfoControllerUT
+                    .getSmscIdentity(0, APPTYPE_ISIM).toString());
+            assertEquals(PSI_SMSC_SIP1, mPhoneSubInfoControllerUT
+                    .getSmscIdentity(0, APPTYPE_USIM).toString());
+            assertEquals(PSI_SMSC_SIP2, mPhoneSubInfoControllerUT
+                    .getSmscIdentity(1, APPTYPE_ISIM).toString());
+            assertEquals(PSI_SMSC_SIP2, mPhoneSubInfoControllerUT
+                    .getSmscIdentity(1, APPTYPE_USIM).toString());
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Test
+    public void testGetSmscIdentityWithOutPermissions() {
+        setUpInitials();
+
+        //case 1: no READ_PRIVILEGED_PHONE_STATE & appOsMgr READ_PHONE_PERMISSION
+        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
+        try {
+            mPhoneSubInfoControllerUT.getSmscIdentity(0, APPTYPE_ISIM);
+            Assert.fail("expected Security Exception Thrown");
+        } catch (Exception ex) {
+            assertTrue(ex instanceof SecurityException);
+            assertTrue(ex.getMessage().contains("getSmscIdentity"));
+        }
+
+        try {
+            mPhoneSubInfoControllerUT.getSmscIdentity(1, APPTYPE_ISIM);
+            Assert.fail("expected Security Exception Thrown");
+        } catch (Exception ex) {
+            assertTrue(ex instanceof SecurityException);
+            assertTrue(ex.getMessage().contains("getSmscIdentity"));
+        }
+
+        try {
+            mPhoneSubInfoControllerUT.getSmscIdentity(0, APPTYPE_USIM);
+            Assert.fail("expected Security Exception Thrown");
+        } catch (Exception ex) {
+            assertTrue(ex instanceof SecurityException);
+            assertTrue(ex.getMessage().contains("getSmscIdentity"));
+        }
+
+        try {
+            mPhoneSubInfoControllerUT.getSmscIdentity(1, APPTYPE_USIM);
+            Assert.fail("expected Security Exception Thrown");
+        } catch (Exception ex) {
+            assertTrue(ex instanceof SecurityException);
+            assertTrue(ex.getMessage().contains("getSmscIdentity"));
+        }
+
+        //case 2: no READ_PRIVILEGED_PHONE_STATE
+        mContextFixture.addCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE);
+        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOp(
+                eq(AppOpsManager.OPSTR_READ_PHONE_STATE), anyInt(), eq(TAG), eq(FEATURE_ID),
+                nullable(String.class));
+
+        try {
+            assertEquals(PSI_SMSC_TEL1, mPhoneSubInfoControllerUT
+                    .getSmscIdentity(0, APPTYPE_ISIM).toString());
+            assertEquals(PSI_SMSC_TEL1, mPhoneSubInfoControllerUT
+                    .getSmscIdentity(0, APPTYPE_USIM).toString());
+            assertEquals(PSI_SMSC_TEL2, mPhoneSubInfoControllerUT
+                    .getSmscIdentity(1, APPTYPE_ISIM).toString());
+            assertEquals(PSI_SMSC_TEL2, mPhoneSubInfoControllerUT
+                    .getSmscIdentity(1, APPTYPE_USIM).toString());
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Test
+    public void testGetSimServiceTable() throws RemoteException {
+        String refSst = "1234567";
+        doReturn(mUiccPort).when(mPhone).getUiccPort();
+        doReturn(mUiccProfile).when(mUiccPort).getUiccProfile();
+        doReturn(mUiccCardApplicationIms).when(mUiccProfile).getApplicationByType(anyInt());
+        doReturn(mSimRecords).when(mUiccCardApplicationIms).getIccRecords();
+
+        doReturn(refSst).when(mSimRecords).getSimServiceTable();
+
+        String resultSst = mPhoneSubInfoControllerUT.getSimServiceTable(anyInt(), anyInt());
+        assertEquals(refSst, resultSst);
+    }
+
+    @Test
+    public void testGetSimServiceTableEmpty() throws RemoteException {
+        String refSst = null;
+        doReturn(mUiccPort).when(mPhone).getUiccPort();
+        doReturn(mUiccProfile).when(mUiccPort).getUiccProfile();
+        doReturn(mUiccCardApplicationIms).when(mUiccProfile).getApplicationByType(anyInt());
+        doReturn(mSimRecords).when(mUiccCardApplicationIms).getIccRecords();
+
+        doReturn(refSst).when(mSimRecords).getSimServiceTable();
+
+        String resultSst = mPhoneSubInfoControllerUT.getSimServiceTable(anyInt(), anyInt());
+        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();
+
+            doReturn(refSst).when(mSimRecords).getSimServiceTable();
+
+            String resultSst = mPhoneSubInfoControllerUT.getSimServiceTable(anyInt(), anyInt());
+            assertEquals(null, resultSst);
+    }
+
+    @Test
+    public void testGetSstWhenNoUiccProfile() throws RemoteException {
+        String refSst = "1234567";
+        doReturn(mUiccPort).when(mPhone).getUiccPort();
+        doReturn(null).when(mUiccPort).getUiccProfile();
+        doReturn(mUiccCardApplicationIms).when(mUiccProfile).getApplicationByType(anyInt());
+        doReturn(mSimRecords).when(mUiccCardApplicationIms).getIccRecords();
+
+        doReturn(refSst).when(mSimRecords).getSimServiceTable();
+
+        String resultSst = mPhoneSubInfoControllerUT.getSimServiceTable(anyInt(), anyInt());
+        assertEquals(null, resultSst);
+    }
+
+    @Test
+    public void testGetSstWhenNoUiccApplication() throws RemoteException {
+        String refSst = "1234567";
+        doReturn(mUiccPort).when(mPhone).getUiccPort();
+        doReturn(mUiccProfile).when(mUiccPort).getUiccProfile();
+        doReturn(null).when(mUiccProfile).getApplicationByType(anyInt());
+        doReturn(mSimRecords).when(mUiccCardApplicationIms).getIccRecords();
+
+        doReturn(refSst).when(mSimRecords).getSimServiceTable();
+
+        String resultSst = mPhoneSubInfoControllerUT.getSimServiceTable(anyInt(), anyInt());
+        assertEquals(null, resultSst);
+    }
+
+    @Test
+    public void testGetSimServiceTableWithOutPermissions() throws RemoteException {
+        String refSst = "1234567";
+        doReturn(mUiccPort).when(mPhone).getUiccPort();
+        doReturn(mUiccProfile).when(mUiccPort).getUiccProfile();
+        doReturn(mUiccCardApplicationIms).when(mUiccProfile).getApplicationByType(anyInt());
+        doReturn(mSimRecords).when(mUiccCardApplicationIms).getIccRecords();
+
+        doReturn(refSst).when(mSimRecords).getSimServiceTable();
+
+        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
+        try {
+            mPhoneSubInfoControllerUT.getSimServiceTable(anyInt(), anyInt());
+            Assert.fail("expected Security Exception Thrown");
+        } catch (Exception ex) {
+            assertTrue(ex instanceof SecurityException);
+            assertTrue(ex.getMessage().contains("getSimServiceTable"));
+        }
+
+        mContextFixture.addCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE);
+        assertEquals(refSst, mPhoneSubInfoControllerUT.getSimServiceTable(anyInt(), anyInt()));
+    }
+
+    @Test
+    public void getPrivateUserIdentity() {
+        String refImpi = "1234567890@example.com";
+        doReturn(mIsimUiccRecords).when(mPhone).getIsimRecords();
+        doReturn(refImpi).when(mIsimUiccRecords).getIsimImpi();
+
+        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOsMgr).noteOpNoThrow(
+                eq(AppOpsManager.OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER), anyInt(), eq(TAG),
+                eq(FEATURE_ID), nullable(String.class));
+
+        String impi = mPhoneSubInfoControllerUT.getImsPrivateUserIdentity(0, TAG, FEATURE_ID);
+        assertEquals(refImpi, impi);
+    }
+
+    @Test
+    public void getPrivateUserIdentity_NoPermission() {
+        String refImpi = "1234567890@example.com";
+        doReturn(mIsimUiccRecords).when(mPhone).getIsimRecords();
+        doReturn(refImpi).when(mIsimUiccRecords).getIsimImpi();
+
+        try {
+            mPhoneSubInfoControllerUT.getImsPrivateUserIdentity(0, TAG, FEATURE_ID);
+            fail();
+        } catch (Exception ex) {
+            assertTrue(ex instanceof SecurityException);
+            assertTrue(ex.getMessage().contains("No permissions to the caller"));
+        }
+    }
+
+    @Test
+    public void getPrivateUserIdentity_InValidSubIdCheck() {
+        String refImpi = "1234567890@example.com";
+        doReturn(mIsimUiccRecords).when(mPhone).getIsimRecords();
+        doReturn(refImpi).when(mIsimUiccRecords).getIsimImpi();
+
+        try {
+            mPhoneSubInfoControllerUT.getImsPrivateUserIdentity(-1, TAG, FEATURE_ID);
+            fail();
+        } catch (Exception ex) {
+            assertTrue(ex instanceof IllegalArgumentException);
+            assertTrue(ex.getMessage().contains("Invalid SubscriptionID"));
+        }
+    }
+
+    @Test
+    public void getImsPublicUserIdentities() {
+        String[] refImpuArray = new String[3];
+        refImpuArray[0] = "012345678";
+        refImpuArray[1] = "sip:test@verify.com";
+        refImpuArray[2] = "tel:+91987754324";
+        doReturn(mIsimUiccRecords).when(mPhone).getIsimRecords();
+        doReturn(refImpuArray).when(mIsimUiccRecords).getIsimImpu();
+
+        List<Uri> impuList = mPhoneSubInfoControllerUT.getImsPublicUserIdentities(0, TAG,
+                FEATURE_ID);
+
+        assertNotNull(impuList);
+        assertEquals(refImpuArray.length, impuList.size());
+        assertEquals(impuList.get(0).toString(), refImpuArray[0]);
+        assertEquals(impuList.get(1).toString(), refImpuArray[1]);
+        assertEquals(impuList.get(2).toString(), refImpuArray[2]);
+    }
+
+    @Test
+    public void getImsPublicUserIdentities_InvalidImpu() {
+        String[] refImpuArray = new String[3];
+        refImpuArray[0] = null;
+        refImpuArray[2] = "";
+        refImpuArray[2] = "tel:+91987754324";
+        doReturn(mIsimUiccRecords).when(mPhone).getIsimRecords();
+        doReturn(refImpuArray).when(mIsimUiccRecords).getIsimImpu();
+        List<Uri> impuList = mPhoneSubInfoControllerUT.getImsPublicUserIdentities(0, TAG,
+                FEATURE_ID);
+        assertNotNull(impuList);
+        // Null or Empty string cannot be converted to URI
+        assertEquals(refImpuArray.length - 2, impuList.size());
+    }
+
+    @Test
+    public void getImsPublicUserIdentities_IsimNotLoadedError() {
+        doReturn(null).when(mPhone).getIsimRecords();
+
+        try {
+            mPhoneSubInfoControllerUT.getImsPublicUserIdentities(0, TAG, FEATURE_ID);
+            fail();
+        } catch (Exception ex) {
+            assertTrue(ex instanceof IllegalStateException);
+            assertTrue(ex.getMessage().contains("ISIM is not loaded"));
+        }
+    }
+
+    @Test
+    public void getImsPublicUserIdentities_InValidSubIdCheck() {
+        try {
+            mPhoneSubInfoControllerUT.getImsPublicUserIdentities(-1, TAG, FEATURE_ID);
+            fail();
+        } catch (Exception ex) {
+            assertTrue(ex instanceof IllegalArgumentException);
+            assertTrue(ex.getMessage().contains("Invalid SubscriptionID"));
+        }
+    }
+
+    @Test
+    public void getImsPublicUserIdentities_NoReadPrivilegedPermission() {
+        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
+        String[] refImpuArray = new String[3];
+        refImpuArray[0] = "012345678";
+        refImpuArray[1] = "sip:test@verify.com";
+        refImpuArray[2] = "tel:+91987754324";
+        doReturn(mIsimUiccRecords).when(mPhone).getIsimRecords();
+        doReturn(refImpuArray).when(mIsimUiccRecords).getIsimImpu();
+
+        List<Uri> impuList = mPhoneSubInfoControllerUT.getImsPublicUserIdentities(0, TAG,
+                FEATURE_ID);
+
+        assertNotNull(impuList);
+        assertEquals(refImpuArray.length, impuList.size());
+        assertEquals(impuList.get(0).toString(), refImpuArray[0]);
+        assertEquals(impuList.get(1).toString(), refImpuArray[1]);
+        assertEquals(impuList.get(2).toString(), refImpuArray[2]);
+        mContextFixture.addCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE);
+    }
+}
\ No newline at end of file
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ProxyControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ProxyControllerTest.java
index c0d497d..65ab664 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ProxyControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ProxyControllerTest.java
@@ -19,10 +19,13 @@
 import static android.telephony.RadioAccessFamily.RAF_GSM;
 import static android.telephony.RadioAccessFamily.RAF_LTE;
 
+import static com.android.internal.telephony.ProxyController.EVENT_FINISH_RC_RESPONSE;
 import static com.android.internal.telephony.ProxyController.EVENT_MULTI_SIM_CONFIG_CHANGED;
 import static com.android.internal.telephony.ProxyController.EVENT_START_RC_RESPONSE;
+import static com.android.internal.telephony.ProxyController.EVENT_TIMEOUT;
 
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
@@ -97,12 +100,139 @@
         rafs[1] = new RadioAccessFamily(1, RAF_GSM | RAF_LTE);
         mProxyController.setRadioCapability(rafs);
 
-        Message.obtain(mProxyController.mHandler, EVENT_START_RC_RESPONSE,
-                new AsyncResult(null, null,
-                        new CommandException(CommandException.Error.REQUEST_NOT_SUPPORTED)))
+        Message.obtain(
+                        mProxyController.mHandler,
+                        EVENT_START_RC_RESPONSE,
+                        new AsyncResult(
+                                null,
+                                null,
+                                new CommandException(CommandException.Error.REQUEST_NOT_SUPPORTED)))
                 .sendToTarget();
         processAllMessages();
 
         assertFalse(mProxyController.isWakeLockHeld());
     }
+
+    @Test
+    @SmallTest
+    public void testWithNonPermanentExceptionOnRCResponse_WithExceptionOnFinishResponse()
+            throws Exception {
+        int activeModemCount = 2;
+        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone, mPhone2});
+        doReturn(activeModemCount).when(mTelephonyManager).getPhoneCount();
+        doReturn(RAF_GSM | RAF_LTE).when(mPhone).getRadioAccessFamily();
+        doReturn(RAF_GSM).when(mPhone2).getRadioAccessFamily();
+
+        Message.obtain(mProxyController.mHandler, EVENT_MULTI_SIM_CONFIG_CHANGED).sendToTarget();
+        processAllMessages();
+        verify(mPhone2).registerForRadioCapabilityChanged(any(), anyInt(), any());
+
+        RadioAccessFamily[] rafs = new RadioAccessFamily[activeModemCount];
+        rafs[0] = new RadioAccessFamily(0, RAF_GSM);
+        rafs[1] = new RadioAccessFamily(1, RAF_GSM | RAF_LTE);
+        mProxyController.setRadioCapability(rafs);
+
+        Message.obtain(
+                        mProxyController.mHandler,
+                        EVENT_START_RC_RESPONSE,
+                        new AsyncResult(
+                                null,
+                                null,
+                                new CommandException(CommandException.Error.RADIO_NOT_AVAILABLE)))
+                .sendToTarget();
+        processAllMessages();
+        assertTrue(mProxyController.isWakeLockHeld());
+        onFinishResponseWithException();
+    }
+
+    @Test
+    @SmallTest
+    public void testWithNonPermanentExceptionOnRCResponse_WithoutExceptionOnFinishResponse()
+            throws Exception {
+        int activeModemCount = 2;
+        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone, mPhone2});
+        doReturn(activeModemCount).when(mTelephonyManager).getPhoneCount();
+        doReturn(RAF_GSM | RAF_LTE).when(mPhone).getRadioAccessFamily();
+        doReturn(RAF_GSM).when(mPhone2).getRadioAccessFamily();
+
+        Message.obtain(mProxyController.mHandler, EVENT_MULTI_SIM_CONFIG_CHANGED).sendToTarget();
+        processAllMessages();
+        verify(mPhone2).registerForRadioCapabilityChanged(any(), anyInt(), any());
+
+        RadioAccessFamily[] rafs = new RadioAccessFamily[activeModemCount];
+        rafs[0] = new RadioAccessFamily(0, RAF_GSM);
+        rafs[1] = new RadioAccessFamily(1, RAF_GSM | RAF_LTE);
+        mProxyController.setRadioCapability(rafs);
+
+        Message.obtain(
+                        mProxyController.mHandler,
+                        EVENT_START_RC_RESPONSE,
+                        new AsyncResult(null, null, null))
+                .sendToTarget();
+        processAllMessages();
+        assertTrue(mProxyController.isWakeLockHeld());
+        onFinishResponseWithoutException();
+    }
+
+    @Test
+    @SmallTest
+    public void testOnRCResponseTimeout_WithExceptionOnFinishResponse() throws Exception {
+        int activeModemCount = 2;
+        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone, mPhone2});
+        doReturn(activeModemCount).when(mTelephonyManager).getPhoneCount();
+        doReturn(RAF_GSM | RAF_LTE).when(mPhone).getRadioAccessFamily();
+        doReturn(RAF_GSM).when(mPhone2).getRadioAccessFamily();
+
+        Message.obtain(mProxyController.mHandler, EVENT_MULTI_SIM_CONFIG_CHANGED).sendToTarget();
+        processAllMessages();
+        verify(mPhone2).registerForRadioCapabilityChanged(any(), anyInt(), any());
+
+        RadioAccessFamily[] rafs = new RadioAccessFamily[activeModemCount];
+        rafs[0] = new RadioAccessFamily(0, RAF_GSM);
+        rafs[1] = new RadioAccessFamily(1, RAF_GSM | RAF_LTE);
+        mProxyController.setRadioCapability(rafs);
+
+        Message.obtain(
+                        mProxyController.mHandler,
+                        EVENT_TIMEOUT,
+                        new AsyncResult(
+                                null,
+                                null,
+                                new CommandException(CommandException.Error.REQUEST_NOT_SUPPORTED)))
+                .sendToTarget();
+        processAllMessages();
+        onFinishResponseWithException();
+    }
+
+    private void onFinishResponseWithException() throws Exception {
+        replaceInstance(
+                ProxyController.class, "mRadioAccessFamilyStatusCounter", mProxyController, 1);
+        replaceInstance(ProxyController.class, "mTransactionFailed", mProxyController, true);
+        Message.obtain(
+                        mProxyController.mHandler,
+                        EVENT_FINISH_RC_RESPONSE,
+                        new AsyncResult(
+                                null,
+                                null,
+                                new CommandException(CommandException.Error.REQUEST_NOT_SUPPORTED)))
+                .sendToTarget();
+        processAllMessages();
+        assertTrue(mProxyController.isWakeLockHeld());
+    }
+
+    private void onFinishResponseWithoutException() throws Exception {
+        replaceInstance(
+                ProxyController.class, "mRadioAccessFamilyStatusCounter", mProxyController, 1);
+        replaceInstance(ProxyController.class, "mTransactionFailed", mProxyController, false);
+        replaceInstance(
+                ProxyController.class, "mRadioCapabilitySessionId", mProxyController, 123456);
+        Message.obtain(
+                        mProxyController.mHandler,
+                        EVENT_FINISH_RC_RESPONSE,
+                        new AsyncResult(
+                                null, new RadioCapability(0, 123456, 0, 0, "test_modem", 0), null))
+                .sendToTarget();
+        processAllMessages();
+        assertFalse(mProxyController.isWakeLockHeld());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/RILTest.java b/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
index 311fe20..2396d1d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
@@ -16,6 +16,12 @@
 
 package com.android.internal.telephony;
 
+import static android.telephony.TelephonyManager.HAL_SERVICE_DATA;
+import static android.telephony.TelephonyManager.HAL_SERVICE_MODEM;
+import static android.telephony.TelephonyManager.HAL_SERVICE_NETWORK;
+import static android.telephony.TelephonyManager.HAL_SERVICE_RADIO;
+import static android.telephony.TelephonyManager.HAL_SERVICE_SIM;
+
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ALLOW_DATA;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_CDMA_GET_SUBSCRIPTION_SOURCE;
@@ -27,6 +33,7 @@
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DATA_REGISTRATION_STATE;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DELETE_SMS_ON_SIM;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DEVICE_IDENTITY;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DEVICE_IMEI;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_DTMF;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ENABLE_UICC_APPLICATIONS;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_ENTER_NETWORK_DEPERSONALIZATION;
@@ -90,6 +97,7 @@
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
 
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -120,12 +128,14 @@
 import android.net.ConnectivityManager;
 import android.net.InetAddresses;
 import android.net.LinkAddress;
+import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.IPowerManager;
 import android.os.IThermalService;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
+import android.os.RemoteException;
 import android.os.WorkSource;
 import android.service.carrier.CarrierIdentifier;
 import android.telephony.AccessNetworkConstants;
@@ -172,6 +182,7 @@
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.PersoSubState;
 
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -183,8 +194,10 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.function.Consumer;
 
@@ -205,14 +218,16 @@
     private RadioDataProxy mDataProxy;
     private RadioNetworkProxy mNetworkProxy;
     private RadioSimProxy mSimProxy;
+    private RadioModemProxy mRadioModemProxy;
 
-    private HalVersion mRadioVersionV10 = new HalVersion(1, 0);
-    private HalVersion mRadioVersionV11 = new HalVersion(1, 1);
-    private HalVersion mRadioVersionV12 = new HalVersion(1, 2);
-    private HalVersion mRadioVersionV13 = new HalVersion(1, 3);
-    private HalVersion mRadioVersionV14 = new HalVersion(1, 4);
-    private HalVersion mRadioVersionV15 = new HalVersion(1, 5);
-    private HalVersion mRadioVersionV16 = new HalVersion(1, 6);
+    private Map<Integer, HalVersion> mHalVersionV10 = new HashMap<>();
+    private Map<Integer, HalVersion> mHalVersionV11 = new HashMap<>();
+    private Map<Integer, HalVersion> mHalVersionV12 = new HashMap<>();
+    private Map<Integer, HalVersion> mHalVersionV13 = new HashMap<>();
+    private Map<Integer, HalVersion> mHalVersionV14 = new HashMap<>();
+    private Map<Integer, HalVersion> mHalVersionV15 = new HashMap<>();
+    private Map<Integer, HalVersion> mHalVersionV16 = new HashMap<>();
+    private Map<Integer, HalVersion> mHalVersionV21 = new HashMap<>();
 
     private RIL mRILInstance;
     private RIL mRILUnderTest;
@@ -300,6 +315,7 @@
         mDataProxy = mock(RadioDataProxy.class);
         mNetworkProxy = mock(RadioNetworkProxy.class);
         mSimProxy = mock(RadioSimProxy.class);
+        mRadioModemProxy = mock(RadioModemProxy.class);
         try {
             TelephonyDevController.create();
         } catch (RuntimeException e) {
@@ -316,10 +332,11 @@
         doReturn(powerManager).when(context).getSystemService(Context.POWER_SERVICE);
         doReturn(new ApplicationInfo()).when(context).getApplicationInfo();
         SparseArray<RadioServiceProxy> proxies = new SparseArray<>();
-        proxies.put(RIL.RADIO_SERVICE, null);
-        proxies.put(RIL.DATA_SERVICE, mDataProxy);
-        proxies.put(RIL.NETWORK_SERVICE, mNetworkProxy);
-        proxies.put(RIL.SIM_SERVICE, mSimProxy);
+        proxies.put(HAL_SERVICE_RADIO, null);
+        proxies.put(HAL_SERVICE_DATA, mDataProxy);
+        proxies.put(HAL_SERVICE_NETWORK, mNetworkProxy);
+        proxies.put(HAL_SERVICE_SIM, mSimProxy);
+        proxies.put(HAL_SERVICE_MODEM, mRadioModemProxy);
         mRILInstance = new RIL(context,
                 RadioAccessFamily.getRafFromNetworkType(RILConstants.PREFERRED_NETWORK_MODE),
                 Phone.PREFERRED_CDMA_SUBSCRIPTION, 0, proxies);
@@ -331,11 +348,24 @@
                 eq(RadioNetworkProxy.class), any());
         doReturn(mSimProxy).when(mRILUnderTest).getRadioServiceProxy(eq(RadioSimProxy.class),
                 any());
+        doReturn(mRadioModemProxy).when(mRILUnderTest).getRadioServiceProxy(
+                eq(RadioModemProxy.class), any());
         doReturn(false).when(mDataProxy).isEmpty();
         doReturn(false).when(mNetworkProxy).isEmpty();
         doReturn(false).when(mSimProxy).isEmpty();
+        doReturn(false).when(mRadioModemProxy).isEmpty();
         try {
-            replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV10);
+            for (int service = RIL.MIN_SERVICE_IDX; service <= RIL.MAX_SERVICE_IDX; service++) {
+                mHalVersionV10.put(service, new HalVersion(1, 0));
+                mHalVersionV11.put(service, new HalVersion(1, 1));
+                mHalVersionV12.put(service, new HalVersion(1, 2));
+                mHalVersionV13.put(service, new HalVersion(1, 3));
+                mHalVersionV14.put(service, new HalVersion(1, 4));
+                mHalVersionV15.put(service, new HalVersion(1, 5));
+                mHalVersionV16.put(service, new HalVersion(1, 6));
+                mHalVersionV21.put(service, new HalVersion(2, 1));
+            }
+            replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV10);
         } catch (Exception e) {
         }
     }
@@ -493,7 +523,7 @@
 
         // Make radio version 1.5 to support the operation.
         try {
-            replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV15);
+            replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV15);
         } catch (Exception e) {
         }
 
@@ -531,7 +561,7 @@
 
         // Make radio version 1.5 to support the operation.
         try {
-            replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV15);
+            replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV15);
         } catch (Exception e) {
         }
 
@@ -711,7 +741,7 @@
     public void testStartNetworkScanWithUnsupportedResponse() throws Exception {
         // Use Radio HAL v1.5
         try {
-            replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV15);
+            replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV15);
         } catch (Exception e) {
         }
         NetworkScanRequest nsr = getNetworkScanRequestForTesting();
@@ -738,7 +768,7 @@
     public void testGetVoiceRegistrationStateWithUnsupportedResponse() throws Exception {
         // Use Radio HAL v1.5
         try {
-            replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV15);
+            replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV15);
         } catch (Exception e) {
         }
         mRILUnderTest.getVoiceRegistrationState(obtainMessage());
@@ -773,7 +803,7 @@
     public void testGetDataRegistrationStateWithUnsupportedResponse() throws Exception {
         // Use Radio HAL v1.5
         try {
-            replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV15);
+            replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV15);
         } catch (Exception e) {
         }
 
@@ -818,7 +848,7 @@
 
         // Use Radio HAL v1.6
         try {
-            replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV16);
+            replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV16);
         } catch (Exception e) {
         }
 
@@ -861,7 +891,7 @@
     public void testSendSMS_1_6() throws Exception {
         // Use Radio HAL v1.6
         try {
-            replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV16);
+            replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV16);
         } catch (Exception e) {
         }
         String smscPdu = "smscPdu";
@@ -893,7 +923,7 @@
     public void testSendSMSExpectMore_1_6() throws Exception {
         // Use Radio HAL v1.6
         try {
-            replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV16);
+            replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV16);
         } catch (Exception e) {
         }
         String smscPdu = "smscPdu";
@@ -912,7 +942,7 @@
     public void testSendCdmaSMS_1_6() throws Exception {
         // Use Radio HAL v1.6
         try {
-            replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV16);
+            replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV16);
         } catch (Exception e) {
         }
         byte[] pdu = "000010020000000000000000000000000000000000".getBytes();
@@ -928,7 +958,7 @@
     public void testSendCdmaSMSExpectMore_1_6() throws Exception {
         // Use Radio HAL v1.6
         try {
-            replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV16);
+            replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV16);
         } catch (Exception e) {
         }
         byte[] pdu = "000010020000000000000000000000000000000000".getBytes();
@@ -1251,7 +1281,7 @@
     @Test
     public void testIccCloseLogicalChannel() throws Exception {
         int channel = 1;
-        mRILUnderTest.iccCloseLogicalChannel(channel, obtainMessage());
+        mRILUnderTest.iccCloseLogicalChannel(channel, false, obtainMessage());
         verify(mRadioProxy).iccCloseLogicalChannel(mSerialNumberCaptor.capture(), eq(channel));
         verifyRILResponse(
                 mRILUnderTest, mSerialNumberCaptor.getValue(), RIL_REQUEST_SIM_CLOSE_CHANNEL);
@@ -1475,7 +1505,7 @@
 
         // Make radio version 1.5 to support the operation.
         try {
-            replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV15);
+            replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV15);
         } catch (Exception e) {
         }
         mRILUnderTest.getBarringInfo(obtainMessage());
@@ -2838,7 +2868,7 @@
 
         // Make radio version 1.5 to support the operation.
         try {
-            replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV15);
+            replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV15);
         } catch (Exception e) {
         }
         mRILUnderTest.enableUiccApplications(false, obtainMessage());
@@ -2855,7 +2885,7 @@
 
         // Make radio version 1.5 to support the operation.
         try {
-            replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV15);
+            replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV15);
         } catch (Exception e) {
         }
         mRILUnderTest.areUiccApplicationsEnabled(obtainMessage());
@@ -2902,7 +2932,7 @@
     public void testGetSlicingConfig() throws Exception {
         // Use Radio HAL v1.6
         try {
-            replaceInstance(RIL.class, "mRadioVersion", mRILUnderTest, mRadioVersionV16);
+            replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV16);
         } catch (Exception e) {
         }
         mRILUnderTest.getSlicingConfig(obtainMessage());
@@ -2910,4 +2940,31 @@
         verifyRILResponse_1_6(
                 mRILUnderTest, mSerialNumberCaptor.getValue(), RIL_REQUEST_GET_SLICING_CONFIG);
     }
+
+    @Test
+    public void getImei() throws RemoteException {
+        try {
+            replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV21);
+        } catch (Exception e) {
+            fail();
+        }
+        mRILUnderTest.getImei(obtainMessage());
+        verify(mRadioModemProxy, atLeast(1)).getImei(mSerialNumberCaptor.capture());
+        verifyRILResponse(mRILUnderTest, mSerialNumberCaptor.getValue(), RIL_REQUEST_DEVICE_IMEI);
+    }
+
+    @Test
+    public void getImeiNotSupported() {
+        try {
+            replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV16);
+        } catch (Exception e) {
+            fail();
+        }
+        Message message = obtainMessage();
+        mRILUnderTest.getImei(message);
+        AsyncResult ar = (AsyncResult) message.obj;
+        Assert.assertEquals(null, ar.result);
+        Assert.assertNotNull(ar.exception.getMessage());
+        Assert.assertEquals("REQUEST_NOT_SUPPORTED", ar.exception.getMessage());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
index fa48f97..379117c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
@@ -92,6 +93,7 @@
 
 import com.android.internal.R;
 import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
+import com.android.internal.telephony.data.AccessNetworksManager;
 import com.android.internal.telephony.data.DataNetworkController;
 import com.android.internal.telephony.metrics.ServiceStateStats;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
@@ -106,6 +108,7 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 
+import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -263,7 +266,6 @@
 
         replaceInstance(ProxyController.class, "sProxyController", null, mProxyController);
         mBundle = mContextFixture.getCarrierConfigBundle();
-
         when(mCarrierConfigManager.getConfigForSubId(anyInt(), any())).thenReturn(mBundle);
         when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(mBundle);
         mBundle.putStringArray(
@@ -276,6 +278,15 @@
                 // UMTS < GPRS < EDGE
                 new String[]{"3,1,2"});
 
+        mBundle.putBoolean(CarrierConfigManager.KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL,
+                false);
+
+        mBundle.putBoolean(CarrierConfigManager.KEY_RATCHET_NR_ADVANCED_BANDWIDTH_IF_RRC_IDLE_BOOL,
+                true);
+
+        doReturn(true).when(mTelephonyManager).isRadioInterfaceCapabilitySupported(
+                TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED);
+
         mSimulatedCommands.setVoiceRegState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
         mSimulatedCommands.setVoiceRadioTech(ServiceState.RIL_RADIO_TECHNOLOGY_HSPA);
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
@@ -294,6 +305,11 @@
         waitUntilReady();
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
+        // Voice radio tech change will always trigger an update of
+        // phone object irrespective of this config
+        mContextFixture.putBooleanResource(
+                com.android.internal.R.bool.config_switch_phone_on_voice_reg_state_change, false);
+
         // Override SPN related resource
         mContextFixture.putResource(
                 com.android.internal.R.string.lockscreen_carrier_default,
@@ -358,6 +374,9 @@
                     15, /* SIGNAL_STRENGTH_GOOD */
                     30  /* SIGNAL_STRENGTH_GREAT */
                 });
+        mBundle.putBoolean(
+                CarrierConfigManager.KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL,
+                true);
 
         sendCarrierConfigUpdate(PHONE_ID);
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
@@ -487,30 +506,37 @@
     @Test
     @MediumTest
     public void testSetRadioPowerForReason() {
+        testSetRadioPowerForReason(TelephonyManager.RADIO_POWER_REASON_THERMAL);
+        testSetRadioPowerForReason(TelephonyManager.RADIO_POWER_REASON_NEARBY_DEVICE);
+        testSetRadioPowerForReason(TelephonyManager.RADIO_POWER_REASON_CARRIER);
+    }
+
+    private void testSetRadioPowerForReason(int reason) {
         // Radio does not turn on if off for other reason and not emergency call.
         assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_ON);
         assertTrue(sst.getRadioPowerOffReasons().isEmpty());
-        sst.setRadioPowerForReason(false, false, false, false, Phone.RADIO_POWER_REASON_THERMAL);
-        assertTrue(sst.getRadioPowerOffReasons().contains(Phone.RADIO_POWER_REASON_THERMAL));
+        sst.setRadioPowerForReason(false, false, false, false, reason);
+        assertTrue(sst.getRadioPowerOffReasons().contains(reason));
         assertTrue(sst.getRadioPowerOffReasons().size() == 1);
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_OFF);
-        sst.setRadioPowerForReason(true, false, false, false, Phone.RADIO_POWER_REASON_USER);
-        assertTrue(sst.getRadioPowerOffReasons().contains(Phone.RADIO_POWER_REASON_THERMAL));
+        sst.setRadioPowerForReason(true, false, false, false,
+                TelephonyManager.RADIO_POWER_REASON_USER);
+        assertTrue(sst.getRadioPowerOffReasons().contains(reason));
         assertTrue(sst.getRadioPowerOffReasons().size() == 1);
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_OFF);
 
         // Radio power state reason is removed and radio turns on if turned on for same reason it
         // had been turned off for.
-        sst.setRadioPowerForReason(true, false, false, false, Phone.RADIO_POWER_REASON_THERMAL);
+        sst.setRadioPowerForReason(true, false, false, false, reason);
         assertTrue(sst.getRadioPowerOffReasons().isEmpty());
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_ON);
 
         // Turn radio off, then successfully turn radio on for emergency call.
-        sst.setRadioPowerForReason(false, false, false, false, Phone.RADIO_POWER_REASON_THERMAL);
-        assertTrue(sst.getRadioPowerOffReasons().contains(Phone.RADIO_POWER_REASON_THERMAL));
+        sst.setRadioPowerForReason(false, false, false, false, reason);
+        assertTrue(sst.getRadioPowerOffReasons().contains(reason));
         assertTrue(sst.getRadioPowerOffReasons().size() == 1);
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_OFF);
@@ -522,33 +548,83 @@
 
     @Test
     @MediumTest
-    public void testSetRadioPowerFromCarrier() {
+    public void testSetRadioPowerForMultipleReasons() {
+        assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_ON);
+        assertTrue(sst.getRadioPowerOffReasons().isEmpty());
+
+        // Turn off radio
+        turnRadioOffForReason(TelephonyManager.RADIO_POWER_REASON_USER, 1);
+        turnRadioOffForReason(TelephonyManager.RADIO_POWER_REASON_THERMAL, 2);
+        turnRadioOffForReason(TelephonyManager.RADIO_POWER_REASON_CARRIER, 3);
+        turnRadioOffForReason(TelephonyManager.RADIO_POWER_REASON_NEARBY_DEVICE, 4);
+
+        // Turn on radio
+        turnRadioOnForReason(TelephonyManager.RADIO_POWER_REASON_NEARBY_DEVICE, 3,
+                TelephonyManager.RADIO_POWER_OFF);
+        turnRadioOnForReason(TelephonyManager.RADIO_POWER_REASON_THERMAL, 2,
+                TelephonyManager.RADIO_POWER_OFF);
+        turnRadioOnForReason(TelephonyManager.RADIO_POWER_REASON_CARRIER, 1,
+                TelephonyManager.RADIO_POWER_OFF);
+        turnRadioOnForReason(TelephonyManager.RADIO_POWER_REASON_USER, 0,
+                TelephonyManager.RADIO_POWER_ON);
+    }
+
+    private void turnRadioOffForReason(int reason, int powerOffReasonSize) {
+        sst.setRadioPowerForReason(false, false, false, false, reason);
+        assertTrue(sst.getRadioPowerOffReasons().contains(reason));
+        assertTrue(sst.getRadioPowerOffReasons().size() == powerOffReasonSize);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+        assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_OFF);
+    }
+
+    private void turnRadioOnForReason(int reason, int powerOffReasonSize,
+            int expectedRadioPowerState) {
+        sst.setRadioPowerForReason(true, false, false, false, reason);
+        assertFalse(sst.getRadioPowerOffReasons().contains(reason));
+        assertTrue(sst.getRadioPowerOffReasons().size() == powerOffReasonSize);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+        assertTrue(mSimulatedCommands.getRadioState() == expectedRadioPowerState);
+    }
+
+    @Test
+    @MediumTest
+    public void testSetRadioPowerForReasonCarrier() {
         // Carrier disable radio power
-        sst.setRadioPowerFromCarrier(false);
+        sst.setRadioPowerForReason(false, false, false, false,
+                TelephonyManager.RADIO_POWER_REASON_CARRIER);
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertFalse(mSimulatedCommands.getRadioState()
                 == TelephonyManager.RADIO_POWER_ON);
-        assertTrue(sst.getDesiredPowerState());
+        assertFalse(sst.getDesiredPowerState());
         assertFalse(sst.getPowerStateFromCarrier());
+        assertTrue(sst.getRadioPowerOffReasons().contains(
+                TelephonyManager.RADIO_POWER_REASON_CARRIER));
+        assertTrue(sst.getRadioPowerOffReasons().size() == 1);
 
         // User toggle radio power will not overrides carrier settings
         sst.setRadioPower(true);
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertFalse(mSimulatedCommands.getRadioState()
                 == TelephonyManager.RADIO_POWER_ON);
-        assertTrue(sst.getDesiredPowerState());
+        assertFalse(sst.getDesiredPowerState());
         assertFalse(sst.getPowerStateFromCarrier());
+        assertTrue(sst.getRadioPowerOffReasons().contains(
+                TelephonyManager.RADIO_POWER_REASON_CARRIER));
+        assertTrue(sst.getRadioPowerOffReasons().size() == 1);
 
         // Carrier re-enable radio power
-        sst.setRadioPowerFromCarrier(true);
+        sst.setRadioPowerForReason(true, false, false, false,
+                TelephonyManager.RADIO_POWER_REASON_CARRIER);
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_ON);
         assertTrue(sst.getDesiredPowerState());
         assertTrue(sst.getPowerStateFromCarrier());
+        assertTrue(sst.getRadioPowerOffReasons().isEmpty());
 
         // User toggle radio power off (airplane mode) and set carrier on
         sst.setRadioPower(false);
-        sst.setRadioPowerFromCarrier(true);
+        sst.setRadioPowerForReason(true, false, false, false,
+                TelephonyManager.RADIO_POWER_REASON_CARRIER);
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertFalse(mSimulatedCommands.getRadioState()
                 == TelephonyManager.RADIO_POWER_ON);
@@ -850,13 +926,15 @@
     @Test
     @MediumTest
     public void testUpdatePhoneType() {
+        String brandOverride = "spn from brand override";
+        doReturn(brandOverride).when(mUiccProfile).getOperatorBrandOverride();
         doReturn(false).when(mPhone).isPhoneTypeGsm();
         doReturn(true).when(mPhone).isPhoneTypeCdmaLte();
         doReturn(CdmaSubscriptionSourceManager.SUBSCRIPTION_FROM_RUIM).when(mCdmaSSM).
                 getCdmaSubscriptionSource();
 
-        logd("Calling updatePhoneType");
         // switch to CDMA
+        logd("Calling updatePhoneType");
         sst.updatePhoneType();
 
         ArgumentCaptor<Integer> integerArgumentCaptor = ArgumentCaptor.forClass(Integer.class);
@@ -901,7 +979,6 @@
         mSimulatedCommands.setVoiceRegState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
         mSimulatedCommands.notifyNetworkStateChanged();
-
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // verify if registered handler has message posted to it
@@ -1083,7 +1160,6 @@
         mSimulatedCommands.notifyNetworkStateChanged();
 
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
-
         // verify if registered handler has message posted to it
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
         verify(mTestHandler).sendMessageAtTime(messageArgumentCaptor.capture(), anyLong());
@@ -1207,7 +1283,7 @@
 
     @Test
     @MediumTest
-    public void testRegisterForVoiceRegStateOrRatChange() {
+    public void testRegisterForVoiceRegStateOrRatChange() throws Exception {
         NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
                 .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
                 .setDomain(NetworkRegistrationInfo.DOMAIN_CS)
@@ -1215,14 +1291,17 @@
                 .build();
         sst.mSS.addNetworkRegistrationInfo(nri);
 
+        sst.mSS.setState(ServiceState.STATE_IN_SERVICE);
         sst.registerForVoiceRegStateOrRatChanged(mTestHandler, EVENT_VOICE_RAT_CHANGED, null);
 
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // Verify if message was posted to handler and value of result
         ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
-        verify(mTestHandler).sendMessageAtTime(messageArgumentCaptor.capture(), anyLong());
+        verify(mTestHandler)
+                .sendMessageAtTime(messageArgumentCaptor.capture(), anyLong());
         assertEquals(EVENT_VOICE_RAT_CHANGED, messageArgumentCaptor.getValue().what);
+
         assertEquals(new Pair<Integer, Integer>(ServiceState.STATE_IN_SERVICE,
                         ServiceState.RIL_RADIO_TECHNOLOGY_LTE),
                 ((AsyncResult)messageArgumentCaptor.getValue().obj).result);
@@ -1317,6 +1396,7 @@
         mSimulatedCommands.setDataRegState(NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
         mSimulatedCommands.notifyNetworkStateChanged();
 
+        waitForDelayedHandlerAction(mSSTTestHandler.getThreadHandler(), 500, 200);
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // verify if registered handler has message posted to it
@@ -1468,14 +1548,11 @@
 
     @Test
     @SmallTest
-    public void testSetPsNotifications() {
+    public void testSetPsNotifications() throws Exception {
         int subId = 1;
         sst.mSubId = subId;
         doReturn(subId).when(mSubInfo).getSubscriptionId();
 
-        doReturn(mSubInfo).when(mSubscriptionController).getActiveSubscriptionInfo(
-                anyInt(), anyString(), nullable(String.class));
-
         final NotificationManager nm = (NotificationManager)
                 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
         mContextFixture.putBooleanResource(
@@ -1486,6 +1563,9 @@
         when(mockResources.getDrawable(anyInt(), any())).thenReturn(mockDrawable);
 
         mContextFixture.putResource(com.android.internal.R.string.RestrictedOnDataTitle, "test1");
+        // Make sure getState() condition returns in service, without this at logs at times found to
+        // be out of service
+        sst.mSS.setState(ServiceState.STATE_IN_SERVICE);
         sst.setNotification(ServiceStateTracker.PS_ENABLED);
         ArgumentCaptor<Notification> notificationArgumentCaptor =
                 ArgumentCaptor.forClass(Notification.class);
@@ -1501,12 +1581,10 @@
 
     @Test
     @SmallTest
-    public void testSetCsNotifications() {
+    public void testSetCsNotifications() throws Exception {
         int subId = 1;
         sst.mSubId = subId;
         doReturn(subId).when(mSubInfo).getSubscriptionId();
-        doReturn(mSubInfo).when(mSubscriptionController)
-                .getActiveSubscriptionInfo(anyInt(), anyString(), nullable(String.class));
 
         final NotificationManager nm = (NotificationManager)
                 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
@@ -1519,6 +1597,9 @@
 
         mContextFixture.putResource(com.android.internal.R.string.RestrictedOnAllVoiceTitle,
                 "test2");
+        // Make sure getState() condition returns in service, without this at logs at times found to
+        // be out of service
+        sst.mSS.setState(ServiceState.STATE_IN_SERVICE);
         sst.setNotification(ServiceStateTracker.CS_ENABLED);
         ArgumentCaptor<Notification> notificationArgumentCaptor =
                 ArgumentCaptor.forClass(Notification.class);
@@ -1534,12 +1615,10 @@
 
     @Test
     @SmallTest
-    public void testSetCsNormalNotifications() {
+    public void testSetCsNormalNotifications() throws Exception {
         int subId = 1;
         sst.mSubId = subId;
         doReturn(subId).when(mSubInfo).getSubscriptionId();
-        doReturn(mSubInfo).when(mSubscriptionController)
-                .getActiveSubscriptionInfo(anyInt(), anyString(), nullable(String.class));
 
         final NotificationManager nm = (NotificationManager)
                 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
@@ -1551,6 +1630,9 @@
         when(mockResources.getDrawable(anyInt(), any())).thenReturn(mockDrawable);
 
         mContextFixture.putResource(com.android.internal.R.string.RestrictedOnNormalTitle, "test3");
+        // Make sure getState() condition returns in service, without this at logs at times found to
+        // be out of service
+        sst.mSS.setState(ServiceState.STATE_IN_SERVICE);
         sst.setNotification(ServiceStateTracker.CS_NORMAL_ENABLED);
         ArgumentCaptor<Notification> notificationArgumentCaptor =
                 ArgumentCaptor.forClass(Notification.class);
@@ -1566,12 +1648,10 @@
 
     @Test
     @SmallTest
-    public void testSetCsEmergencyNotifications() {
+    public void testSetCsEmergencyNotifications() throws Exception {
         int subId = 1;
         sst.mSubId = subId;
         doReturn(subId).when(mSubInfo).getSubscriptionId();
-        doReturn(mSubInfo).when(mSubscriptionController)
-                .getActiveSubscriptionInfo(anyInt(), anyString(), nullable(String.class));
 
         final NotificationManager nm = (NotificationManager)
                 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
@@ -1584,6 +1664,9 @@
 
         mContextFixture.putResource(com.android.internal.R.string.RestrictedOnEmergencyTitle,
                 "test4");
+        // Make sure mIsEmergencyOnly should be true, when setNotification notification type is
+        // CS_EMERGENCY_ENABLED notification
+        sst.mSS.setEmergencyOnly(true);
         sst.setNotification(ServiceStateTracker.CS_EMERGENCY_ENABLED);
         ArgumentCaptor<Notification> notificationArgumentCaptor =
                 ArgumentCaptor.forClass(Notification.class);
@@ -2201,16 +2284,16 @@
     }
 
     @Test
-    public void testPhyChanBandwidthRatchetedOnPhyChanBandwidth() throws Exception {
+    public void testPhyChanBandwidthRatchetedOnPhyChanBandwidth() {
         // LTE Cell with bandwidth = 10000
         CellIdentityLte cellIdentity10 =
                 new CellIdentityLte(1, 1, 1, 1, new int[] {1, 2}, 10000, "1", "1", "test",
                         "tst", Collections.emptyList(), null);
 
         sendRegStateUpdateForLteCellId(cellIdentity10);
-        assertTrue(Arrays.equals(new int[] {10000}, sst.mSS.getCellBandwidths()));
+        assertArrayEquals(new int[]{10000}, sst.mSS.getCellBandwidths());
         sendPhyChanConfigChange(new int[] {10000, 5000}, TelephonyManager.NETWORK_TYPE_LTE, 1);
-        assertTrue(Arrays.equals(new int[] {10000, 5000}, sst.mSS.getCellBandwidths()));
+        assertArrayEquals(new int[]{10000, 5000}, sst.mSS.getCellBandwidths());
     }
 
     @Test
@@ -2222,7 +2305,43 @@
     }
 
     @Test
-    public void testPhyChanBandwidthResetsOnOos() throws Exception {
+    public void testNotifyServiceStateChangedOnPhysicalConfigUpdates() {
+        mBundle.putBoolean(
+                CarrierConfigManager.KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL,
+                true);
+
+        // LTE Cell with bandwidth = 10000
+        CellIdentityLte cellIdentity11 =
+                new CellIdentityLte(1, 1, 1, 1, new int[] {1, 2}, 10000, "1", "1", "test",
+                        "tst", Collections.emptyList(), null);
+
+        sendRegStateUpdateForLteCellId(cellIdentity11);
+
+        // Notify Service State changed for BandWidth Change
+        sendPhyChanConfigChange(new int[] {10000, 40000}, TelephonyManager.NETWORK_TYPE_LTE, 1);
+        verify(mPhone, times(1)).notifyServiceStateChanged(any(ServiceState.class));
+
+        // Notify Service State changed for NR Connection Status: LTE -> NR
+        // with context id update
+        when(mPhone.getDataNetworkController().isInternetNetwork(eq(3))).thenReturn(true);
+        sendPhyChanConfigChange(new int[] {10000, 40000}, TelephonyManager.NETWORK_TYPE_NR, 1,
+                new int[][]{{0, 1}, {2, 3}});
+        verify(mPhone, times(2)).notifyServiceStateChanged(any(ServiceState.class));
+
+        // Service State changed is not notified since no change
+        when(mPhone.getDataNetworkController().isInternetNetwork(eq(3))).thenReturn(true);
+        sendPhyChanConfigChange(new int[] {10000, 40000}, TelephonyManager.NETWORK_TYPE_NR, 1,
+                new int[][]{{0, 1}, {2, 3}});
+        verify(mPhone, times(2)).notifyServiceStateChanged(any(ServiceState.class));
+
+        // Notify Service State changed for NR Connection Status: NR -> LTE
+        when(mPhone.getDataNetworkController().isInternetNetwork(eq(3))).thenReturn(true);
+        sendPhyChanConfigChange(new int[] {10000, 40000}, TelephonyManager.NETWORK_TYPE_LTE, 1);
+        verify(mPhone, times(3)).notifyServiceStateChanged(any(ServiceState.class));
+    }
+
+    @Test
+    public void testPhyChanBandwidthResetsOnOos() {
         testPhyChanBandwidthRatchetedOnPhyChanBandwidth();
         LteVopsSupportInfo lteVopsSupportInfo =
                 new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE,
@@ -2250,13 +2369,21 @@
 
     @Test
     public void testPhyChanBandwidthForNr() {
+        mBundle.putBoolean(
+                CarrierConfigManager.KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL,
+                false);
         // NR Cell with bandwidth = 10000
         CellIdentityNr nrCi = new CellIdentityNr(
-                0, 0, 0, new int[] {}, "", "", 5, "", "", Collections.emptyList());
+                0, 0, 0, new int[]{10000}, "", "", 5, "", "", Collections.emptyList());
 
-        sendPhyChanConfigChange(new int[] {10000, 5000}, TelephonyManager.NETWORK_TYPE_NR, 0);
         sendRegStateUpdateForNrCellId(nrCi);
-        assertTrue(Arrays.equals(new int[] {10000, 5000}, sst.mSS.getCellBandwidths()));
+        // should ratchet for NR
+        sendPhyChanConfigChange(new int[] {10000, 5000}, TelephonyManager.NETWORK_TYPE_NR, 0);
+        assertArrayEquals(new int[]{10000, 5000}, sst.mSS.getCellBandwidths());
+
+        // should not ratchet for LTE
+        sendPhyChanConfigChange(new int[] {100}, TelephonyManager.NETWORK_TYPE_LTE, 0);
+        assertArrayEquals(new int[]{}, sst.mSS.getCellBandwidths());
     }
 
     /**
@@ -2353,6 +2480,9 @@
     public void testLocaleTrackerUpdateWithIWLANInService() {
         // Start state: Cell data only LTE + IWLAN
         final String[] OpNamesResult = new String[] { "carrier long", "carrier", "310310" };
+        // Clear invocations for mLocaleTracker as precondition before test case execution & as part
+        // test setup
+        Mockito.clearInvocations(mLocaleTracker);
         changeRegStateWithIwlanOperatorNumeric(NetworkRegistrationInfo.REGISTRATION_STATE_HOME,
                 TelephonyManager.NETWORK_TYPE_LTE,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME, OpNamesResult, true);
@@ -2736,13 +2866,20 @@
     @Test
     public void testUpdateSpnDisplay_spnNotEmptyAndCrossSimCallingEnabled_showSpnOnly() {
         // GSM phone
-
         doReturn(true).when(mPhone).isPhoneTypeGsm();
+        final CellIdentityLte cellIdentityLte =
+                new CellIdentityLte(1, 1, 5, 1, new int[] {1, 2}, 5000, "001", "01", "test",
+                        "tst", Collections.emptyList(), null);
 
         // In Service
         ServiceState ss = new ServiceState();
         ss.setVoiceRegState(ServiceState.STATE_IN_SERVICE);
         ss.setDataRegState(ServiceState.STATE_IN_SERVICE);
+        //To By Pass RatRacheter
+        ss.addNetworkRegistrationInfo(makeNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                cellIdentityLte, true));
         sst.mSS = ss;
 
         // cross-sim-calling is enable
@@ -2771,11 +2908,19 @@
     public void testUpdateSpnDisplay_spnNotEmptyAndWifiCallingEnabled_showSpnOnly() {
         // GSM phone
         doReturn(true).when(mPhone).isPhoneTypeGsm();
+        final CellIdentityLte cellIdentityLte =
+                new CellIdentityLte(1, 1, 5, 1, new int[] {1, 2}, 5000, "001", "01", "test",
+                        "tst", Collections.emptyList(), null);
 
         // In Service
         ServiceState ss = new ServiceState();
         ss.setVoiceRegState(ServiceState.STATE_IN_SERVICE);
         ss.setDataRegState(ServiceState.STATE_IN_SERVICE);
+        //To By Pass RatRacheter
+        ss.addNetworkRegistrationInfo(makeNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                cellIdentityLte, true));
         sst.mSS = ss;
 
         // wifi-calling is enabled
@@ -3012,6 +3157,7 @@
     @Test
     public void testGetCombinedRegState() {
         doReturn(mImsPhone).when(mPhone).getImsPhone();
+        doReturn(true).when(mPhone).isPhoneTypeGsm();
 
         // If voice/data out of service, return out of service.
         doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mServiceState).getState();
@@ -3051,4 +3197,35 @@
         doReturn(ServiceState.STATE_EMERGENCY_ONLY).when(mServiceState).getState();
         assertEquals(ServiceState.STATE_EMERGENCY_ONLY, sst.getCombinedRegState(mServiceState));
     }
+
+
+    @Test
+    public void testOnChangingPreferredOnIwlan() throws Exception {
+        Field callbackField =
+                ServiceStateTracker.class.getDeclaredField("mAccessNetworksManagerCallback");
+        callbackField.setAccessible(true);
+        AccessNetworksManager.AccessNetworksManagerCallback accessNetworksManagerCallback =
+                (AccessNetworksManager.AccessNetworksManagerCallback) callbackField.get(sst);
+
+        when(mAccessNetworksManager.isAnyApnOnIwlan()).thenReturn(false);
+
+        // Start state: Cell data only LTE + IWLAN
+        CellIdentityLte cellIdentity =
+                new CellIdentityLte(1, 1, 5, 1, new int[] {1, 2}, 5000, "001", "01", "test",
+                        "tst", Collections.emptyList(), null);
+        changeRegStateWithIwlan(
+                // WWAN
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, cellIdentity,
+                TelephonyManager.NETWORK_TYPE_UNKNOWN, TelephonyManager.NETWORK_TYPE_LTE,
+                // WLAN
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME,
+                TelephonyManager.NETWORK_TYPE_IWLAN);
+        assertFalse(sst.mSS.isIwlanPreferred());
+
+        when(mAccessNetworksManager.isAnyApnOnIwlan()).thenReturn(true);
+        accessNetworksManagerCallback.onPreferredTransportChanged(0);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+
+        assertTrue(sst.mSS.isIwlanPreferred());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthControllerTest.java
index 0391a81..e4617fe 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthControllerTest.java
@@ -18,7 +18,9 @@
 
 import static android.telephony.SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP;
 import static android.telephony.SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI;
+import static android.telephony.SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP;
 import static android.telephony.SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR;
+import static android.telephony.TelephonyManager.HAL_SERVICE_NETWORK;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -27,6 +29,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -110,6 +113,7 @@
                         -97, /* SIGNAL_STRENGTH_GOOD */
                         -89,  /* SIGNAL_STRENGTH_GREAT */
                 });
+        mBundle.putInt(CarrierConfigManager.KEY_GERAN_RSSI_HYSTERESIS_DB_INT, 6);
         // Support EUTRAN with RSRP
         mBundle.putInt(CarrierConfigManager.KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT,
                 1 /* USE_RSRP */);
@@ -120,6 +124,7 @@
                         -95, /* SIGNAL_STRENGTH_GOOD */
                         -85,  /* SIGNAL_STRENGTH_GREAT */
                 });
+        mBundle.putInt(CarrierConfigManager.KEY_EUTRAN_RSRP_HYSTERESIS_DB_INT, 3);
         // Support NR with SSRSRP
         mBundle.putInt(CarrierConfigManager.KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT,
                 1 /* USE_SSRSRP */);
@@ -130,6 +135,7 @@
                         -80, /* SIGNAL_STRENGTH_GOOD */
                         -64,  /* SIGNAL_STRENGTH_GREAT */
                 });
+        mBundle.putInt(CarrierConfigManager.KEY_NGRAN_SSRSRP_HYSTERESIS_DB_INT, 1);
         // By default, NR with SSRSRQ and SSSINR is not supported
         mBundle.putIntArray(CarrierConfigManager.KEY_5G_NR_SSRSRQ_THRESHOLDS_INT_ARRAY,
                 new int[] {
@@ -515,6 +521,212 @@
     }
 
     @Test
+    public void testSetMinimumHysteresisDb_FromThresholdDelta() {
+        final int[] consolidatedThresholdList = new int[] {-120, -116, -113, -112};
+
+        SignalThresholdInfo info =
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN)
+                        .setSignalMeasurementType(SIGNAL_MEASUREMENT_TYPE_RSSI)
+                        .setThresholds(new int[] {-113}, true)
+                        .setHysteresisDb(2)
+                        .build();
+        SignalStrengthUpdateRequest request =
+                createTestSignalStrengthUpdateRequest(
+                        info,
+                        false /* shouldReportWhileIdle*/,
+                        false /* shouldReportSystemWhileIdle */);
+        mSsc.setSignalStrengthUpdateRequest(
+                ACTIVE_SUB_ID, CALLING_UID, request, Message.obtain(mHandler));
+        processAllMessages();
+
+        int minHysteresis =
+                mSsc.getMinimumHysteresisDb(true,
+                        AccessNetworkConstants.AccessNetworkType.GERAN,
+                        SIGNAL_MEASUREMENT_TYPE_RSSI,
+                        consolidatedThresholdList);
+        assertEquals(1, minHysteresis);
+        mSsc.clearSignalStrengthUpdateRequest(
+                ACTIVE_SUB_ID, CALLING_UID, request, Message.obtain(mHandler));
+        processAllMessages();
+    }
+
+    @Test
+    public void testSetMinimumHysteresisDb_FromSignalThresholdRequest() {
+        final int[] consolidatedThresholdList = new int[] {-120, -116, -112, -108};
+
+        SignalThresholdInfo info =
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.EUTRAN)
+                        .setSignalMeasurementType(SIGNAL_MEASUREMENT_TYPE_RSRP)
+                        .setThresholds(new int[] {-113}, true)
+                        .setHysteresisDb(3)
+                        .build();
+        SignalStrengthUpdateRequest request =
+                createTestSignalStrengthUpdateRequest(
+                        info,
+                        false /* shouldReportWhileIdle*/,
+                        false /* shouldReportSystemWhileIdle */);
+        mSsc.setSignalStrengthUpdateRequest(
+                ACTIVE_SUB_ID, CALLING_UID, request, Message.obtain(mHandler));
+        processAllMessages();
+
+        int minHysteresis =
+                mSsc.getMinimumHysteresisDb(true,
+                        AccessNetworkConstants.AccessNetworkType.EUTRAN,
+                        SIGNAL_MEASUREMENT_TYPE_RSRP,
+                        consolidatedThresholdList);
+        assertEquals(3, minHysteresis);
+
+        mSsc.clearSignalStrengthUpdateRequest(
+                ACTIVE_SUB_ID, CALLING_UID, request, Message.obtain(mHandler));
+        processAllMessages();
+    }
+
+    @Test
+    public void testSetMinimumHysteresisDb_FromCarrierConfig() {
+        final int[] consolidatedThresholdList = new int[] {-120, -115, -108, -103};
+
+        SignalThresholdInfo info =
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.NGRAN)
+                        .setSignalMeasurementType(SIGNAL_MEASUREMENT_TYPE_SSRSRP)
+                        .setThresholds(new int[] {-113}, true)
+                        .setHysteresisDb(6)
+                        .build();
+        SignalStrengthUpdateRequest request =
+                createTestSignalStrengthUpdateRequest(
+                        info,
+                        false /* shouldReportWhileIdle*/,
+                        false /* shouldReportSystemWhileIdle */);
+        mSsc.setSignalStrengthUpdateRequest(
+                ACTIVE_SUB_ID, CALLING_UID, request, Message.obtain(mHandler));
+        processAllMessages();
+
+        int minHysteresis =
+                mSsc.getMinimumHysteresisDb(true,
+                        AccessNetworkConstants.AccessNetworkType.NGRAN,
+                        SIGNAL_MEASUREMENT_TYPE_SSRSRP,
+                        consolidatedThresholdList);
+        assertEquals(1, minHysteresis);
+        mSsc.clearSignalStrengthUpdateRequest(
+                ACTIVE_SUB_ID, CALLING_UID, request, Message.obtain(mHandler));
+        processAllMessages();
+    }
+
+    @Test
+    public void testSetHysteresisDb_WithCarrierConfigValue() {
+        when(mPhone.isDeviceIdle()).thenReturn(true);
+        when(mPhone.getSubId()).thenReturn(ACTIVE_SUB_ID);
+
+        mBundle.putInt(CarrierConfigManager.KEY_GERAN_RSSI_HYSTERESIS_DB_INT, 5);
+        mBundle.putInt(CarrierConfigManager.KEY_EUTRAN_RSRP_HYSTERESIS_DB_INT, 3);
+        mBundle.putInt(CarrierConfigManager.KEY_NGRAN_SSRSRP_HYSTERESIS_DB_INT, 2);
+        sendCarrierConfigUpdate();
+
+        ArgumentCaptor<List<SignalThresholdInfo>> signalThresholdInfoCaptor =
+                ArgumentCaptor.forClass(List.class);
+        verify(mSimulatedCommandsVerifier, atLeastOnce())
+                .setSignalStrengthReportingCriteria(signalThresholdInfoCaptor.capture(), isNull());
+        List<SignalThresholdInfo> capturedInfos = signalThresholdInfoCaptor.getAllValues().get(0);
+        assertThat(capturedInfos).isNotEmpty();
+
+        for (SignalThresholdInfo signalThresholdInfo : capturedInfos) {
+            if (signalThresholdInfo.getSignalMeasurementType() == SIGNAL_MEASUREMENT_TYPE_RSRP) {
+                assertEquals(3, signalThresholdInfo.getHysteresisDb());
+            }
+            if (signalThresholdInfo.getSignalMeasurementType() == SIGNAL_MEASUREMENT_TYPE_RSSI) {
+                assertEquals(5, signalThresholdInfo.getHysteresisDb());
+            }
+            if (signalThresholdInfo.getSignalMeasurementType() == SIGNAL_MEASUREMENT_TYPE_SSRSRP) {
+                assertEquals(2, signalThresholdInfo.getHysteresisDb());
+            }
+        }
+        reset(mSimulatedCommandsVerifier);
+    }
+
+    @Test
+    public void testSetHysteresisDb_BetweenCarrierConfigSignalThresholdInfoThresholdDelta() {
+        SignalThresholdInfo info =
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.NGRAN)
+                        .setSignalMeasurementType(SIGNAL_MEASUREMENT_TYPE_SSRSRP)
+                        .setThresholds(new int[] {-116}, true)
+                        .setHysteresisDb(3)
+                        .build();
+        SignalStrengthUpdateRequest request =
+                createTestSignalStrengthUpdateRequest(
+                        info,
+                        false /* shouldReportWhileIdle*/,
+                        false /* shouldReportSystemWhileIdle */);
+        mSsc.setSignalStrengthUpdateRequest(
+                ACTIVE_SUB_ID, CALLING_UID, request, Message.obtain(mHandler));
+        processAllMessages();
+
+        reset(mSimulatedCommandsVerifier);
+        when(mPhone.isDeviceIdle()).thenReturn(false);
+        when(mPhone.getSubId()).thenReturn(ACTIVE_SUB_ID);
+        mBundle.putIntArray(CarrierConfigManager.KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY,
+                new int[] {
+                        -113, /* SIGNAL_STRENGTH_POOR */
+                        -107, /* SIGNAL_STRENGTH_MODERATE */
+                        -100, /* SIGNAL_STRENGTH_GOOD */
+                        -95,  /* SIGNAL_STRENGTH_GREAT */
+                });
+
+        mBundle.putInt(CarrierConfigManager.KEY_PARAMETERS_USE_FOR_5G_NR_SIGNAL_BAR_INT,
+                1 /* USE_SSRSRP */);
+        mBundle.putInt(CarrierConfigManager.KEY_NGRAN_SSRSRP_HYSTERESIS_DB_INT, 4);
+        sendCarrierConfigUpdate();
+
+        ArgumentCaptor<List<SignalThresholdInfo>> signalThresholdInfoCaptor =
+                ArgumentCaptor.forClass(List.class);
+        verify(mSimulatedCommandsVerifier, atLeastOnce())
+                .setSignalStrengthReportingCriteria(signalThresholdInfoCaptor.capture(), isNull());
+        List<SignalThresholdInfo> capturedInfos = signalThresholdInfoCaptor.getAllValues().get(0);
+        assertThat(capturedInfos).isNotEmpty();
+
+        for (SignalThresholdInfo signalThresholdInfo : capturedInfos) {
+            if (signalThresholdInfo.getSignalMeasurementType() == SIGNAL_MEASUREMENT_TYPE_SSRSRP) {
+                assertEquals(4,
+                        mBundle.getInt(CarrierConfigManager.KEY_NGRAN_SSRSRP_HYSTERESIS_DB_INT));
+                assertEquals(3, signalThresholdInfo.getHysteresisDb());
+            }
+        }
+    }
+
+    @Test
+    public void testSetHysteresisDb_WithInvalidCarrierConfigValue() {
+        when(mPhone.isDeviceIdle()).thenReturn(true);
+        when(mPhone.getSubId()).thenReturn(ACTIVE_SUB_ID);
+
+        mBundle.putInt(CarrierConfigManager.KEY_GERAN_RSSI_HYSTERESIS_DB_INT, -4);
+        mBundle.putInt(CarrierConfigManager.KEY_EUTRAN_RSRP_HYSTERESIS_DB_INT, -5);
+        mBundle.putInt(CarrierConfigManager.KEY_NGRAN_SSRSRP_HYSTERESIS_DB_INT, -2);
+        sendCarrierConfigUpdate();
+
+        ArgumentCaptor<List<SignalThresholdInfo>> signalThresholdInfoCaptor =
+                ArgumentCaptor.forClass(List.class);
+        verify(mSimulatedCommandsVerifier, atLeastOnce())
+                .setSignalStrengthReportingCriteria(signalThresholdInfoCaptor.capture(), isNull());
+        List<SignalThresholdInfo> capturedInfos = signalThresholdInfoCaptor.getAllValues().get(0);
+        assertThat(capturedInfos).isNotEmpty();
+
+        for (SignalThresholdInfo signalThresholdInfo : capturedInfos) {
+            if (signalThresholdInfo.getSignalMeasurementType() == SIGNAL_MEASUREMENT_TYPE_RSRP) {
+                assertEquals(2, signalThresholdInfo.getHysteresisDb());
+            }
+            if (signalThresholdInfo.getSignalMeasurementType() == SIGNAL_MEASUREMENT_TYPE_RSSI) {
+                assertEquals(2, signalThresholdInfo.getHysteresisDb());
+            }
+            if (signalThresholdInfo.getSignalMeasurementType() == SIGNAL_MEASUREMENT_TYPE_SSRSRP) {
+                assertEquals(2, signalThresholdInfo.getHysteresisDb());
+            }
+        }
+        reset(mSimulatedCommandsVerifier);
+    }
+
+    @Test
     public void testLteSignalStrengthReportingCriteria_convertRssnrUnitFromTenDbToDB() {
         SignalStrength ss = new SignalStrength(
                 new CellSignalStrengthCdma(),
@@ -817,7 +1029,7 @@
             }
         }
         // Only check on RADIO hal 1.5 and above to make it less flaky
-        if (mPhone.getHalVersion().greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) {
+        if (mPhone.getHalVersion(HAL_SERVICE_NETWORK).greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) {
             assertThat(expectedNonEmptyThreshold).isEqualTo(actualNonEmptyThreshold);
         }
     }
@@ -864,4 +1076,4 @@
         }
         return builder.build();
     }
-}
+}
\ No newline at end of file
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthTest.java b/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthTest.java
index f15845c..96184c5 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SignalStrengthTest.java
@@ -98,7 +98,7 @@
                 new CellSignalStrengthWcdma(-94, 4, -102, -5),
                 new CellSignalStrengthTdscdma(-95, 2, -103),
                 new CellSignalStrengthLte(-85, -91, -6, -10, 1, 12, 1),
-                new CellSignalStrengthNr(-91, -6, 3, 1, NrCqiReport, -80, -7, 4));
+                new CellSignalStrengthNr(-91, -6, 3, 1, NrCqiReport, -80, -7, 4, 1));
         assertParcelingIsLossless(s);
 
         PersistableBundle bundle = new PersistableBundle();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SignalThresholdInfoTest.java b/tests/telephonytests/src/com/android/internal/telephony/SignalThresholdInfoTest.java
index b282c55..e22d7ab 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SignalThresholdInfoTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SignalThresholdInfoTest.java
@@ -42,69 +42,94 @@
 public class SignalThresholdInfoTest extends TestCase {
     private static final int HYSTERESIS_DB = 2;
     private static final int HYSTERESIS_MS = 30;
-    private static final int[] SSRSRP_THRESHOLDS = new int[]{-120, -100, -80, -60};
+    private static final int[] SSRSRP_THRESHOLDS = new int[] {-120, -100, -80, -60};
 
     // Map of SignalMeasurementType to invalid thresholds edge values.
     // Each invalid value will be constructed with a thresholds array to test separately.
-    private static final Map<Integer, List<Integer>> INVALID_THRESHOLDS_MAP = Map.of(
-            SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI,
-            List.of(SignalThresholdInfo.SIGNAL_RSSI_MIN_VALUE - 1,
-                    SignalThresholdInfo.SIGNAL_RSSI_MAX_VALUE + 1),
-            SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP,
-            List.of(SignalThresholdInfo.SIGNAL_RSCP_MIN_VALUE - 1,
-                    SignalThresholdInfo.SIGNAL_RSCP_MAX_VALUE + 1),
-            SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP,
-            List.of(SignalThresholdInfo.SIGNAL_RSRP_MIN_VALUE - 1,
-                    SignalThresholdInfo.SIGNAL_RSRP_MAX_VALUE + 1),
-            SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ,
-            List.of(SignalThresholdInfo.SIGNAL_RSRQ_MIN_VALUE - 1,
-                    SignalThresholdInfo.SIGNAL_RSRQ_MAX_VALUE + 1),
-            SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR,
-            List.of(SignalThresholdInfo.SIGNAL_RSSNR_MIN_VALUE - 1,
-                    SignalThresholdInfo.SIGNAL_RSSNR_MAX_VALUE + 1),
-            SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP,
-            List.of(SignalThresholdInfo.SIGNAL_SSRSRP_MIN_VALUE - 1,
-                    SignalThresholdInfo.SIGNAL_SSRSRP_MAX_VALUE + 1),
-            SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ,
-            List.of(SignalThresholdInfo.SIGNAL_SSRSRQ_MIN_VALUE - 1,
-                    SignalThresholdInfo.SIGNAL_SSRSRQ_MAX_VALUE + 1),
-            SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR,
-            List.of(SignalThresholdInfo.SIGNAL_SSSINR_MIN_VALUE - 1,
-                    SignalThresholdInfo.SIGNAL_SSSINR_MAX_VALUE + 1)
-    );
+    private static final Map<Integer, List<Integer>> INVALID_THRESHOLDS_MAP =
+            Map.of(
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI,
+                    List.of(
+                            SignalThresholdInfo.SIGNAL_RSSI_MIN_VALUE - 1,
+                            SignalThresholdInfo.SIGNAL_RSSI_MAX_VALUE + 1),
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP,
+                    List.of(
+                            SignalThresholdInfo.SIGNAL_RSCP_MIN_VALUE - 1,
+                            SignalThresholdInfo.SIGNAL_RSCP_MAX_VALUE + 1),
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP,
+                    List.of(
+                            SignalThresholdInfo.SIGNAL_RSRP_MIN_VALUE - 1,
+                            SignalThresholdInfo.SIGNAL_RSRP_MAX_VALUE + 1),
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ,
+                    List.of(
+                            SignalThresholdInfo.SIGNAL_RSRQ_MIN_VALUE - 1,
+                            SignalThresholdInfo.SIGNAL_RSRQ_MAX_VALUE + 1),
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR,
+                    List.of(
+                            SignalThresholdInfo.SIGNAL_RSSNR_MIN_VALUE - 1,
+                            SignalThresholdInfo.SIGNAL_RSSNR_MAX_VALUE + 1),
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP,
+                    List.of(
+                            SignalThresholdInfo.SIGNAL_SSRSRP_MIN_VALUE - 1,
+                            SignalThresholdInfo.SIGNAL_SSRSRP_MAX_VALUE + 1),
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ,
+                    List.of(
+                            SignalThresholdInfo.SIGNAL_SSRSRQ_MIN_VALUE - 1,
+                            SignalThresholdInfo.SIGNAL_SSRSRQ_MAX_VALUE + 1),
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR,
+                    List.of(
+                            SignalThresholdInfo.SIGNAL_SSSINR_MIN_VALUE - 1,
+                            SignalThresholdInfo.SIGNAL_SSSINR_MAX_VALUE + 1),
+                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_ECNO,
+                    List.of(
+                            SignalThresholdInfo.SIGNAL_ECNO_MIN_VALUE - 1,
+                            SignalThresholdInfo.SIGNAL_ECNO_MAX_VALUE + 1));
 
     // Map of RAN to allowed SignalMeasurementType set.
     // RAN/TYPE pair will be used to verify the validation of the combo
-    private static final Map<Integer, Set<Integer>> VALID_RAN_TO_MEASUREMENT_TYPE_MAP = Map.of(
-            AccessNetworkConstants.AccessNetworkType.GERAN,
-            Set.of(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI),
-            AccessNetworkConstants.AccessNetworkType.CDMA2000,
-            Set.of(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI),
-            AccessNetworkConstants.AccessNetworkType.UTRAN,
-            Set.of(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP),
-            AccessNetworkConstants.AccessNetworkType.EUTRAN,
-            Set.of(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP,
-                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ,
-                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR),
-            AccessNetworkConstants.AccessNetworkType.NGRAN,
-            Set.of(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP,
-                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ,
-                    SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR)
-    );
+    private static final Map<Integer, Set<Integer>> VALID_RAN_TO_MEASUREMENT_TYPE_MAP =
+            Map.of(
+                    AccessNetworkConstants.AccessNetworkType.GERAN,
+                    Set.of(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI),
+                    AccessNetworkConstants.AccessNetworkType.CDMA2000,
+                    Set.of(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI),
+                    AccessNetworkConstants.AccessNetworkType.UTRAN,
+                    Set.of(
+                            SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP,
+                            SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_ECNO),
+                    AccessNetworkConstants.AccessNetworkType.EUTRAN,
+                    Set.of(
+                            SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP,
+                            SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ,
+                            SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR),
+                    AccessNetworkConstants.AccessNetworkType.NGRAN,
+                    Set.of(
+                            SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP,
+                            SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ,
+                            SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR));
 
     // Deliberately picking up the max/min value in each range to test the edge cases
-    private final int[] mRssiThresholds = new int[]{-113, -103, -97, -51};
-    private final int[] mRscpThresholds = new int[]{-120, -105, -95, -25};
-    private final int[] mRsrpThresholds = new int[]{-140, -118, -108, -44};
-    private final int[] mRsrqThresholds = new int[]{-34, -17, -14, 3};
-    private final int[] mRssnrThresholds = new int[]{-20, 10, 20, 30};
-    private final int[] mSsrsrpThresholds = new int[]{-140, -118, -98, -44};
-    private final int[] mSsrsrqThresholds = new int[]{-43, -17, -14, 20};
-    private final int[] mSssinrThresholds = new int[]{-23, -16, -10, 40};
+    private final int[] mRssiThresholds = new int[] {-113, -103, -97, -51};
+    private final int[] mRscpThresholds = new int[] {-120, -105, -95, -25};
+    private final int[] mRsrpThresholds = new int[] {-140, -118, -108, -44};
+    private final int[] mRsrqThresholds = new int[] {-34, -17, -14, 3};
+    private final int[] mRssnrThresholds = new int[] {-20, 10, 20, 30};
+    private final int[] mSsrsrpThresholds = new int[] {-140, -118, -98, -44};
+    private final int[] mSsrsrqThresholds = new int[] {-43, -17, -14, 20};
+    private final int[] mSssinrThresholds = new int[] {-23, -16, -10, 40};
+    private final int[] mEcnoThresholds = new int[] {-24, -16, -8, 1};
 
-    private final int[][] mThresholds = {mRssiThresholds, mRscpThresholds, mRsrpThresholds,
-            mRsrqThresholds, mRssnrThresholds, mSsrsrpThresholds, mSsrsrqThresholds,
-            mSssinrThresholds};
+    private final int[][] mThresholds = {
+        mRssiThresholds,
+        mRscpThresholds,
+        mRsrpThresholds,
+        mRsrqThresholds,
+        mRssnrThresholds,
+        mSsrsrpThresholds,
+        mSsrsrqThresholds,
+        mSssinrThresholds,
+        mEcnoThresholds
+    };
 
     @Test
     @SmallTest
@@ -120,12 +145,14 @@
                         .setIsEnabled(false)
                         .build();
 
-        assertEquals(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP,
+        assertEquals(
+                SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP,
                 signalThresholdInfo.getSignalMeasurementType());
         assertEquals(HYSTERESIS_MS, signalThresholdInfo.getHysteresisMs());
         assertEquals(HYSTERESIS_DB, signalThresholdInfo.getHysteresisDb());
-        assertEquals(Arrays.toString(SSRSRP_THRESHOLDS), Arrays.toString(
-                signalThresholdInfo.getThresholds()));
+        assertEquals(
+                Arrays.toString(SSRSRP_THRESHOLDS),
+                Arrays.toString(signalThresholdInfo.getThresholds()));
         assertFalse(signalThresholdInfo.isEnabled());
     }
 
@@ -160,68 +187,127 @@
     @SmallTest
     public void testGetSignalThresholdInfo() {
         ArrayList<SignalThresholdInfo> stList = new ArrayList<>();
-        stList.add(new SignalThresholdInfo.Builder()
-                .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN)
-                .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI)
-                .setHysteresisMs(0)
-                .setHysteresisDb(0)
-                .setThresholds(new int[]{}, true /*isSystem*/)
-                .setIsEnabled(false)
-                .build());
-        stList.add(new SignalThresholdInfo.Builder()
-                .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN)
-                .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI)
-                .setHysteresisMs(HYSTERESIS_MS).setHysteresisDb(HYSTERESIS_DB)
-                .setThresholds(mRssiThresholds)
-                .setIsEnabled(false)
-                .build());
+        stList.add(
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN)
+                        .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI)
+                        .setHysteresisMs(0)
+                        .setHysteresisDb(0)
+                        .setThresholds(new int[] {}, true /*isSystem*/)
+                        .setIsEnabled(false)
+                        .build());
+        stList.add(
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN)
+                        .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI)
+                        .setHysteresisMs(HYSTERESIS_MS)
+                        .setHysteresisDb(HYSTERESIS_DB)
+                        .setThresholds(mRssiThresholds)
+                        .setIsEnabled(false)
+                        .build());
 
-        assertThat(stList.get(0).getThresholds()).isEqualTo(new int[]{});
-        assertThat(stList.get(1).getSignalMeasurementType()).isEqualTo(
-                SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI);
+        assertThat(stList.get(0).getThresholds()).isEqualTo(new int[] {});
+        assertThat(stList.get(1).getSignalMeasurementType())
+                .isEqualTo(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI);
         assertThat(stList.get(1).getThresholds()).isEqualTo(mRssiThresholds);
+        assertThat(stList.get(0).getHysteresisDb())
+                .isEqualTo(SignalThresholdInfo.HYSTERESIS_DB_MINIMUM);
+        assertThat(stList.get(1).getHysteresisDb())
+                .isEqualTo(HYSTERESIS_DB);
     }
 
     @Test
     @SmallTest
     public void testEqualsSignalThresholdInfo() {
-        final int[] dummyThresholds = new int[]{-100, -90, -70, -60};
-        final int[] dummyThreholdsDisordered = new int[]{-60, -90, -100, -70};
-        SignalThresholdInfo st1 = new SignalThresholdInfo.Builder()
-                .setRadioAccessNetworkType(1).setSignalMeasurementType(1)
-                .setHysteresisMs(HYSTERESIS_MS).setHysteresisDb(HYSTERESIS_DB)
-                .setThresholds(mRssiThresholds).setIsEnabled(false)
-                .build();
-        SignalThresholdInfo st2 = new SignalThresholdInfo.Builder().setRadioAccessNetworkType(2)
-                .setSignalMeasurementType(2).setHysteresisMs(HYSTERESIS_MS)
-                .setHysteresisDb(HYSTERESIS_DB).setThresholds(mRssiThresholds).setIsEnabled(false)
-                .build();
-        SignalThresholdInfo st3 = new SignalThresholdInfo.Builder().setRadioAccessNetworkType(1)
-                .setSignalMeasurementType(1).setHysteresisMs(HYSTERESIS_MS)
-                .setHysteresisDb(HYSTERESIS_DB).setThresholds(dummyThresholds).setIsEnabled(false)
-                .build();
-        SignalThresholdInfo st4 = new SignalThresholdInfo.Builder().setRadioAccessNetworkType(1)
-                .setSignalMeasurementType(1).setHysteresisMs(HYSTERESIS_MS)
-                .setHysteresisDb(HYSTERESIS_DB).setThresholds(mRssiThresholds).setIsEnabled(false)
-                .build();
-        SignalThresholdInfo st5 = new SignalThresholdInfo.Builder().setRadioAccessNetworkType(1)
-                .setSignalMeasurementType(1).setHysteresisMs(HYSTERESIS_MS)
-                .setHysteresisDb(HYSTERESIS_DB).setThresholds(dummyThreholdsDisordered)
-                .setIsEnabled(false).build();
+        final int[] dummyThresholds = new int[] {-100, -90, -70, -60};
+        final int[] dummyThresholdsDisordered = new int[] {-60, -90, -100, -70};
+        SignalThresholdInfo st1 =
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(1)
+                        .setSignalMeasurementType(1)
+                        .setHysteresisMs(HYSTERESIS_MS)
+                        .setHysteresisDb(HYSTERESIS_DB)
+                        .setThresholds(mRssiThresholds)
+                        .setIsEnabled(false)
+                        .build();
+        SignalThresholdInfo st2 =
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(2)
+                        .setSignalMeasurementType(2)
+                        .setHysteresisMs(HYSTERESIS_MS)
+                        .setHysteresisDb(HYSTERESIS_DB)
+                        .setThresholds(mRssiThresholds)
+                        .setIsEnabled(false)
+                        .build();
+        SignalThresholdInfo st3 =
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(1)
+                        .setSignalMeasurementType(1)
+                        .setHysteresisMs(HYSTERESIS_MS)
+                        .setHysteresisDb(HYSTERESIS_DB)
+                        .setThresholds(dummyThresholds)
+                        .setIsEnabled(false)
+                        .build();
+        SignalThresholdInfo st4 =
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(1)
+                        .setSignalMeasurementType(1)
+                        .setHysteresisMs(HYSTERESIS_MS)
+                        .setHysteresisDb(HYSTERESIS_DB)
+                        .setThresholds(mRssiThresholds)
+                        .setIsEnabled(false)
+                        .build();
+        SignalThresholdInfo st5 =
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(1)
+                        .setSignalMeasurementType(1)
+                        .setHysteresisMs(HYSTERESIS_MS)
+                        .setHysteresisDb(HYSTERESIS_DB)
+                        .setThresholds(dummyThresholdsDisordered)
+                        .setIsEnabled(false)
+                        .build();
 
-        //Return true if all SignalThresholdInfo values match.
+        // Return true if all SignalThresholdInfo values match.
         assertTrue(st1.equals(st1));
         assertFalse(st1.equals(st2));
         assertFalse(st1.equals(st3));
         assertTrue(st1.equals(st4));
-        //Threshold values ordering doesn't matter
+        // Threshold values ordering doesn't matter
         assertTrue(st3.equals(st5));
-        //Return false if the object of argument is other than SignalThresholdInfo.
+        // Return false if the object of argument is other than SignalThresholdInfo.
         assertFalse(st1.equals(new String("test")));
     }
 
     @Test
     @SmallTest
+    public void testHysteresisDbSettings_WithValidRange() {
+        SignalThresholdInfo st1 =
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN)
+                        .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI)
+                        .setThresholds(new int[] {}, true)
+                        .build();
+        SignalThresholdInfo st2 =
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN)
+                        .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI)
+                        .setThresholds(new int[] {}, true)
+                        .setHysteresisDb(3)
+                        .build();
+        SignalThresholdInfo st3 =
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN)
+                        .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI)
+                        .setThresholds(new int[] {}, true)
+                        .setHysteresisDb(1)
+                        .build();
+        assertThat(st1.getHysteresisDb()).isEqualTo(HYSTERESIS_DB);
+        assertThat(st2.getHysteresisDb()).isEqualTo(3);
+        assertThat(st3.getHysteresisDb()).isEqualTo(1);
+    }
+
+    @Test
+    @SmallTest
     public void testBuilderWithValidParameters() {
         ArrayList<SignalThresholdInfo> stList = buildSignalThresholdInfoWithPublicFields();
 
@@ -229,7 +315,7 @@
             SignalThresholdInfo st = stList.get(i);
             assertThat(st.getThresholds()).isEqualTo(mThresholds[i]);
             assertThat(st.getHysteresisMs()).isEqualTo(SignalThresholdInfo.HYSTERESIS_MS_DISABLED);
-            assertThat(st.getHysteresisDb()).isEqualTo(SignalThresholdInfo.HYSTERESIS_DB_DISABLED);
+            assertThat(st.getHysteresisDb()).isEqualTo(HYSTERESIS_DB);
             assertFalse(st.isEnabled());
         }
     }
@@ -238,33 +324,46 @@
     @SmallTest
     public void testBuilderWithInvalidParameter() {
         // Invalid signal measurement type
-        int[] invalidSignalMeasurementTypes = new int[]{-1, 0, 9};
+        int[] invalidSignalMeasurementTypes = new int[] {-1, 0, 9};
         for (int signalMeasurementType : invalidSignalMeasurementTypes) {
             buildWithInvalidParameterThrowException(
-                    AccessNetworkConstants.AccessNetworkType.GERAN, signalMeasurementType,
-                    new int[]{-1});
+                    AccessNetworkConstants.AccessNetworkType.GERAN,
+                    signalMeasurementType,
+                    new int[] {-1}, 2);
         }
 
         // Null thresholds array
-        buildWithInvalidParameterThrowException(AccessNetworkConstants.AccessNetworkType.GERAN,
-                SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI, null);
+        buildWithInvalidParameterThrowException(
+                AccessNetworkConstants.AccessNetworkType.GERAN,
+                SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI,
+                null, 0);
 
         // Empty thresholds
-        buildWithInvalidParameterThrowException(AccessNetworkConstants.AccessNetworkType.GERAN,
-                SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI, new int[]{});
-
+        buildWithInvalidParameterThrowException(
+                AccessNetworkConstants.AccessNetworkType.GERAN,
+                SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI,
+                new int[] {}, 5);
 
         // Too long thresholds array
-        buildWithInvalidParameterThrowException(AccessNetworkConstants.AccessNetworkType.GERAN,
+        buildWithInvalidParameterThrowException(
+                AccessNetworkConstants.AccessNetworkType.GERAN,
                 SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI,
-                new int[]{-100, -90, -70, -60, -58});
+                new int[] {-100, -90, -70, -60, -58}, 3);
+
+        // Test Hysteresis Db invalid Range
+        buildWithInvalidParameterThrowException(
+                AccessNetworkConstants.AccessNetworkType.GERAN,
+                SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI,
+                new int[] {-100, -90, -70, -60}, -1);
 
         // Thresholds value out of range
         for (int signalMeasurementType : INVALID_THRESHOLDS_MAP.keySet()) {
             List<Integer> invalidThresholds = INVALID_THRESHOLDS_MAP.get(signalMeasurementType);
             for (int threshold : invalidThresholds) {
-                buildWithInvalidParameterThrowException(getValidRan(signalMeasurementType),
-                        signalMeasurementType, new int[]{threshold});
+                buildWithInvalidParameterThrowException(
+                        getValidRan(signalMeasurementType),
+                        signalMeasurementType,
+                        new int[] {threshold}, 1);
             }
         }
 
@@ -272,21 +371,23 @@
         for (int ran : VALID_RAN_TO_MEASUREMENT_TYPE_MAP.keySet()) {
             Set validTypes = VALID_RAN_TO_MEASUREMENT_TYPE_MAP.get(ran);
             for (int type = SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI;
-                    type <= SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR; type++) {
+                    type <= SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_ECNO;
+                    type++) {
                 if (!validTypes.contains(type)) {
-                    buildWithInvalidParameterThrowException(ran, type, new int[]{-1});
+                    buildWithInvalidParameterThrowException(ran, type, new int[] {-1}, 2);
                 }
             }
         }
     }
 
-    private void buildWithInvalidParameterThrowException(int ran, int signalMeasurementType,
-            int[] thresholds) {
+    private void buildWithInvalidParameterThrowException(
+            int ran, int signalMeasurementType, int[] thresholds, int hysteresisDb) {
         try {
             new SignalThresholdInfo.Builder()
                     .setRadioAccessNetworkType(ran)
                     .setSignalMeasurementType(signalMeasurementType)
                     .setThresholds(thresholds)
+                    .setHysteresisDb(hysteresisDb)
                     .build();
             fail("exception expected");
         } catch (IllegalArgumentException | NullPointerException expected) {
@@ -296,68 +397,90 @@
     private ArrayList<SignalThresholdInfo> buildSignalThresholdInfoWithAllFields() {
         ArrayList<SignalThresholdInfo> stList = new ArrayList<>();
 
-        stList.add(new SignalThresholdInfo.Builder()
-                .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN)
-                .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI)
-                .setHysteresisMs(HYSTERESIS_MS).setHysteresisDb(HYSTERESIS_DB)
-                .setThresholds(mRssiThresholds).setIsEnabled(false)
-                .build());
-        stList.add(new SignalThresholdInfo.Builder()
-                .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.UTRAN)
-                .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP)
-                .setHysteresisMs(HYSTERESIS_MS)
-                .setHysteresisDb(HYSTERESIS_DB)
-                .setThresholds(mRscpThresholds)
-                .setIsEnabled(false)
-                .build());
-        stList.add(new SignalThresholdInfo.Builder()
-                .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.EUTRAN)
-                .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP)
-                .setHysteresisMs(HYSTERESIS_MS)
-                .setHysteresisDb(HYSTERESIS_DB)
-                .setThresholds(mRsrpThresholds)
-                .setIsEnabled(false)
-                .build());
-        stList.add(new SignalThresholdInfo.Builder()
-                .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.EUTRAN)
-                .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ)
-                .setHysteresisMs(HYSTERESIS_MS)
-                .setHysteresisDb(HYSTERESIS_DB)
-                .setThresholds(mRsrqThresholds)
-                .setIsEnabled(false)
-                .build());
-        stList.add(new SignalThresholdInfo.Builder()
-                .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.EUTRAN)
-                .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR)
-                .setHysteresisMs(HYSTERESIS_MS)
-                .setHysteresisDb(HYSTERESIS_DB)
-                .setThresholds(mRssnrThresholds)
-                .setIsEnabled(false)
-                .build());
-        stList.add(new SignalThresholdInfo.Builder()
-                .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.NGRAN)
-                .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP)
-                .setHysteresisMs(HYSTERESIS_MS)
-                .setHysteresisDb(HYSTERESIS_DB)
-                .setThresholds(mSsrsrpThresholds)
-                .setIsEnabled(false)
-                .build());
-        stList.add(new SignalThresholdInfo.Builder()
-                .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.NGRAN)
-                .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ)
-                .setHysteresisMs(HYSTERESIS_MS)
-                .setHysteresisDb(HYSTERESIS_DB)
-                .setThresholds(mSsrsrqThresholds)
-                .setIsEnabled(false)
-                .build());
-        stList.add(new SignalThresholdInfo.Builder()
-                .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.NGRAN)
-                .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR)
-                .setHysteresisMs(HYSTERESIS_MS)
-                .setHysteresisDb(HYSTERESIS_DB)
-                .setThresholds(mSssinrThresholds)
-                .setIsEnabled(false)
-                .build());
+        stList.add(
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN)
+                        .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI)
+                        .setHysteresisMs(HYSTERESIS_MS)
+                        .setHysteresisDb(HYSTERESIS_DB)
+                        .setThresholds(mRssiThresholds)
+                        .setIsEnabled(false)
+                        .build());
+        stList.add(
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.UTRAN)
+                        .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP)
+                        .setHysteresisMs(HYSTERESIS_MS)
+                        .setHysteresisDb(HYSTERESIS_DB)
+                        .setThresholds(mRscpThresholds)
+                        .setIsEnabled(false)
+                        .build());
+        stList.add(
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.EUTRAN)
+                        .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP)
+                        .setHysteresisMs(HYSTERESIS_MS)
+                        .setHysteresisDb(HYSTERESIS_DB)
+                        .setThresholds(mRsrpThresholds)
+                        .setIsEnabled(false)
+                        .build());
+        stList.add(
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.EUTRAN)
+                        .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ)
+                        .setHysteresisMs(HYSTERESIS_MS)
+                        .setHysteresisDb(HYSTERESIS_DB)
+                        .setThresholds(mRsrqThresholds)
+                        .setIsEnabled(false)
+                        .build());
+        stList.add(
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.EUTRAN)
+                        .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR)
+                        .setHysteresisMs(HYSTERESIS_MS)
+                        .setHysteresisDb(HYSTERESIS_DB)
+                        .setThresholds(mRssnrThresholds)
+                        .setIsEnabled(false)
+                        .build());
+        stList.add(
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.NGRAN)
+                        .setSignalMeasurementType(
+                                SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP)
+                        .setHysteresisMs(HYSTERESIS_MS)
+                        .setHysteresisDb(HYSTERESIS_DB)
+                        .setThresholds(mSsrsrpThresholds)
+                        .setIsEnabled(false)
+                        .build());
+        stList.add(
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.NGRAN)
+                        .setSignalMeasurementType(
+                                SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ)
+                        .setHysteresisMs(HYSTERESIS_MS)
+                        .setHysteresisDb(HYSTERESIS_DB)
+                        .setThresholds(mSsrsrqThresholds)
+                        .setIsEnabled(false)
+                        .build());
+        stList.add(
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.NGRAN)
+                        .setSignalMeasurementType(
+                                SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR)
+                        .setHysteresisMs(HYSTERESIS_MS)
+                        .setHysteresisDb(HYSTERESIS_DB)
+                        .setThresholds(mSssinrThresholds)
+                        .setIsEnabled(false)
+                        .build());
+        stList.add(
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.UTRAN)
+                        .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_ECNO)
+                        .setHysteresisMs(HYSTERESIS_MS)
+                        .setHysteresisDb(HYSTERESIS_DB)
+                        .setThresholds(mEcnoThresholds)
+                        .setIsEnabled(false)
+                        .build());
 
         return stList;
     }
@@ -365,46 +488,63 @@
     private ArrayList<SignalThresholdInfo> buildSignalThresholdInfoWithPublicFields() {
         ArrayList<SignalThresholdInfo> stList = new ArrayList<>();
 
-        stList.add(new SignalThresholdInfo.Builder()
-                .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN)
-                .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI)
-                .setThresholds(mRssiThresholds)
-                .build());
-        stList.add(new SignalThresholdInfo.Builder()
-                .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.UTRAN)
-                .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP)
-                .setThresholds(mRscpThresholds)
-                .build());
-        stList.add(new SignalThresholdInfo.Builder()
-                .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.EUTRAN)
-                .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP)
-                .setThresholds(mRsrpThresholds)
-                .build());
-        stList.add(new SignalThresholdInfo.Builder()
-                .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.EUTRAN)
-                .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ)
-                .setThresholds(mRsrqThresholds)
-                .build());
-        stList.add(new SignalThresholdInfo.Builder()
-                .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.EUTRAN)
-                .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR)
-                .setThresholds(mRssnrThresholds)
-                .build());
-        stList.add(new SignalThresholdInfo.Builder()
-                .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.NGRAN)
-                .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP)
-                .setThresholds(mSsrsrpThresholds)
-                .build());
-        stList.add(new SignalThresholdInfo.Builder()
-                .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.NGRAN)
-                .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ)
-                .setThresholds(mSsrsrqThresholds)
-                .build());
-        stList.add(new SignalThresholdInfo.Builder()
-                .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.NGRAN)
-                .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR)
-                .setThresholds(mSssinrThresholds)
-                .build());
+        stList.add(
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.GERAN)
+                        .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI)
+                        .setThresholds(mRssiThresholds)
+                        .build());
+        stList.add(
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.UTRAN)
+                        .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP)
+                        .setThresholds(mRscpThresholds)
+                        .build());
+        stList.add(
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.EUTRAN)
+                        .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP)
+                        .setThresholds(mRsrpThresholds)
+                        .build());
+        stList.add(
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.EUTRAN)
+                        .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ)
+                        .setThresholds(mRsrqThresholds)
+                        .build());
+        stList.add(
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.EUTRAN)
+                        .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSNR)
+                        .setThresholds(mRssnrThresholds)
+                        .build());
+        stList.add(
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.NGRAN)
+                        .setSignalMeasurementType(
+                                SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRP)
+                        .setThresholds(mSsrsrpThresholds)
+                        .build());
+        stList.add(
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.NGRAN)
+                        .setSignalMeasurementType(
+                                SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSRSRQ)
+                        .setThresholds(mSsrsrqThresholds)
+                        .build());
+        stList.add(
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.NGRAN)
+                        .setSignalMeasurementType(
+                                SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_SSSINR)
+                        .setThresholds(mSssinrThresholds)
+                        .build());
+        stList.add(
+                new SignalThresholdInfo.Builder()
+                        .setRadioAccessNetworkType(AccessNetworkConstants.AccessNetworkType.UTRAN)
+                        .setSignalMeasurementType(SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_ECNO)
+                        .setThresholds(mEcnoThresholds)
+                        .build());
 
         return stList;
     }
@@ -418,6 +558,7 @@
             case SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI:
                 return AccessNetworkConstants.AccessNetworkType.GERAN;
             case SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSCP:
+            case SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_ECNO:
                 return AccessNetworkConstants.AccessNetworkType.UTRAN;
             case SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRP:
             case SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSRQ:
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommands.java b/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommands.java
index 6a075da..1e4c939 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommands.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommands.java
@@ -21,6 +21,7 @@
 import android.hardware.radio.V1_0.DataRegStateResult;
 import android.hardware.radio.V1_0.SetupDataCallResult;
 import android.hardware.radio.V1_0.VoiceRegStateResult;
+import android.hardware.radio.modem.ImeiInfo;
 import android.net.KeepalivePacketData;
 import android.net.LinkProperties;
 import android.os.AsyncResult;
@@ -66,6 +67,7 @@
 import com.android.internal.telephony.RILUtils;
 import com.android.internal.telephony.RadioCapability;
 import com.android.internal.telephony.SmsResponse;
+import com.android.internal.telephony.SrvccConnection;
 import com.android.internal.telephony.UUSInfo;
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
@@ -186,8 +188,17 @@
     public boolean mSetRadioPowerForEmergencyCall;
     public boolean mSetRadioPowerAsSelectedPhoneForEmergencyCall;
 
+    public boolean mCallWaitActivated = false;
+    private SrvccConnection[] mSrvccConnections;
+
     // mode for Icc Sim Authentication
     private int mAuthenticationMode;
+
+    private int[] mImsRegistrationInfo = new int[4];
+
+    private boolean mN1ModeEnabled = false;
+    private boolean mVonrEnabled = false;
+
     //***** Constructor
     public
     SimulatedCommands() {
@@ -1432,6 +1443,14 @@
 
     @Override
     public void queryCallWaiting(int serviceClass, Message response) {
+        if (response != null && serviceClass == SERVICE_CLASS_NONE) {
+            int[] r = new int[2];
+            r[0] = (mCallWaitActivated ? 1 : 0);
+            r[1] = (mCallWaitActivated ? SERVICE_CLASS_VOICE : SERVICE_CLASS_NONE);
+            resultSuccess(response, r);
+            return;
+        }
+
         unimplemented(response);
     }
 
@@ -1440,11 +1459,15 @@
      * @param serviceClass is a sum of SERVICE_CLASS_*
      * @param response is callback message
      */
-
     @Override
     public void setCallWaiting(boolean enable, int serviceClass,
             Message response) {
-        unimplemented(response);
+        if ((serviceClass & SERVICE_CLASS_VOICE) == SERVICE_CLASS_VOICE) {
+            mCallWaitActivated = enable;
+        }
+        if (response != null) {
+            resultSuccess(response, null);
+        }
     }
 
     /**
@@ -1477,12 +1500,17 @@
     }
 
     @Override
-    public void setNetworkSelectionModeAutomatic(Message result) {unimplemented(result);}
+    public void setNetworkSelectionModeAutomatic(Message result) {
+        SimulatedCommandsVerifier.getInstance().setNetworkSelectionModeAutomatic(result);
+        mMockNetworkSelectionMode = 0;
+    }
     @Override
     public void exitEmergencyCallbackMode(Message result) {unimplemented(result);}
     @Override
     public void setNetworkSelectionModeManual(String operatorNumeric, int ran, Message result) {
-        unimplemented(result);
+        SimulatedCommandsVerifier.getInstance().setNetworkSelectionModeManual(
+                operatorNumeric, ran, result);
+        mMockNetworkSelectionMode = 1;
     }
 
     /**
@@ -1499,10 +1527,13 @@
         getNetworkSelectionModeCallCount.incrementAndGet();
         int ret[] = new int[1];
 
-        ret[0] = 0;
+        ret[0] = mMockNetworkSelectionMode;
         resultSuccess(result, ret);
     }
 
+    /** 0 for automatic selection and a 1 for manual selection. */
+    private int mMockNetworkSelectionMode = 0;
+
     private final AtomicInteger getNetworkSelectionModeCallCount = new AtomicInteger(0);
 
     @VisibleForTesting
@@ -1792,6 +1823,16 @@
     }
 
     @Override
+    public void getImei(Message response) {
+        SimulatedCommandsVerifier.getInstance().getImei(response);
+        ImeiInfo imeiInfo = new ImeiInfo();
+        imeiInfo.imei = FAKE_IMEI;
+        imeiInfo.svn = FAKE_IMEISV;
+        imeiInfo.type = ImeiInfo.ImeiType.SECONDARY;
+        resultSuccess(response, imeiInfo);
+    }
+
+    @Override
     public void
     getCDMASubscription(Message result) {
         String ret[] = new String[5];
@@ -1891,8 +1932,8 @@
 
     @Override
     public void setCdmaBroadcastActivation(boolean activate, Message response) {
-        unimplemented(response);
-
+        SimulatedCommandsVerifier.getInstance().setCdmaBroadcastActivation(activate, response);
+        resultSuccess(response, null);
     }
 
     @Override
@@ -1903,7 +1944,8 @@
 
     @Override
     public void setCdmaBroadcastConfig(CdmaSmsBroadcastConfigInfo[] configs, Message response) {
-        unimplemented(response);
+        SimulatedCommandsVerifier.getInstance().setCdmaBroadcastConfig(configs, response);
+        resultSuccess(response, null);
     }
 
     public void forceDataDormancy(Message response) {
@@ -1913,7 +1955,8 @@
 
     @Override
     public void setGsmBroadcastActivation(boolean activate, Message response) {
-        unimplemented(response);
+        SimulatedCommandsVerifier.getInstance().setGsmBroadcastActivation(activate, response);
+        resultSuccess(response, null);
     }
 
 
@@ -1921,7 +1964,7 @@
     public void setGsmBroadcastConfig(SmsBroadcastConfigInfo[] config, Message response) {
         SimulatedCommandsVerifier.getInstance().setGsmBroadcastConfig(config, response);
         if (mSendSetGsmBroadcastConfigResponse) {
-            unimplemented(response);
+            resultSuccess(response, null);
         }
     }
 
@@ -2135,19 +2178,18 @@
     }
 
     @Override
-    public void iccCloseLogicalChannel(int channel, Message response) {
+    public void iccCloseLogicalChannel(int channel, boolean isEs10, Message response) {
         unimplemented(response);
     }
 
     @Override
     public void iccTransmitApduLogicalChannel(int channel, int cla, int instruction,
-                                              int p1, int p2, int p3, String data,
-                                              Message response) {
+            int p1, int p2, int p3, String data, boolean isEs10Command, Message response) {
         SimulatedCommandsVerifier.getInstance().iccTransmitApduLogicalChannel(channel, cla,
-                instruction, p1, p2, p3, data, response);
-        if(mIccIoResultForApduLogicalChannel!=null) {
+                instruction, p1, p2, p3, data, isEs10Command, response);
+        if (mIccIoResultForApduLogicalChannel != null) {
             resultSuccess(response, mIccIoResultForApduLogicalChannel);
-        }else {
+        } else {
             resultFail(response, null, new RuntimeException("IccIoResult not set"));
         }
     }
@@ -2558,4 +2600,44 @@
         PcoData response = new PcoData(cid, bearerProto, pcoId, contents);
         mPcoDataRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
     }
+
+    @Override
+    public void setSrvccCallInfo(SrvccConnection[] srvccConnections, Message result) {
+        mSrvccConnections = srvccConnections;
+    }
+
+    public SrvccConnection[] getSrvccConnections() {
+        return mSrvccConnections;
+    }
+
+    @Override
+    public void updateImsRegistrationInfo(int regState,
+            int imsRadioTech, int suggestedAction, int capabilities, Message result) {
+        mImsRegistrationInfo[0] = regState;
+        mImsRegistrationInfo[1] = imsRadioTech;
+        mImsRegistrationInfo[2] = suggestedAction;
+        mImsRegistrationInfo[3] = capabilities;
+    }
+
+    public int[] getImsRegistrationInfo() {
+        return mImsRegistrationInfo;
+    }
+
+    @Override
+    public void setN1ModeEnabled(boolean enable, Message result) {
+        mN1ModeEnabled = enable;
+    }
+
+    public boolean isN1ModeEnabled() {
+        return mN1ModeEnabled;
+    }
+
+    @Override
+    public void isVoNrEnabled(Message message, WorkSource workSource) {
+        resultSuccess(message, (Object) mVonrEnabled);
+    }
+
+    public void setVonrEnabled(boolean vonrEnable) {
+        mVonrEnabled = vonrEnable;
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommandsVerifier.java b/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommandsVerifier.java
index 60add09..4d1c104 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommandsVerifier.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SimulatedCommandsVerifier.java
@@ -1160,6 +1160,11 @@
     }
 
     @Override
+    public void getImei(Message response) {
+
+    }
+
+    @Override
     public void getCDMASubscription(Message response) {
 
     }
@@ -1299,13 +1304,14 @@
     }
 
     @Override
-    public void iccCloseLogicalChannel(int channel, Message response) {
+    public void iccCloseLogicalChannel(int channel, boolean isEs10, Message response) {
 
     }
 
     @Override
     public void iccTransmitApduLogicalChannel(int channel, int cla, int instruction, int p1,
-                                              int p2, int p3, String data, Message response) {
+                                              int p2, int p3, String data,
+                                              boolean isEs10Command, Message response) {
 
     }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SmsControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SmsControllerTest.java
index a81a252..09c4173 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SmsControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SmsControllerTest.java
@@ -16,6 +16,17 @@
 
 package com.android.internal.telephony;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.pm.PackageManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -29,17 +40,6 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
 
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.assertFalse;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
-
 import java.util.ArrayList;
 
 @RunWith(AndroidTestingRunner.class)
@@ -195,4 +195,48 @@
 
         doReturn(false).when(mPhone).isInEcm();
     }
+
+    @Test
+    public void sendsendTextForSubscriberTest() {
+        int subId = 1;
+        doReturn(true).when(mSubscriptionManager)
+                .isSubscriptionAssociatedWithUser(eq(subId), any());
+
+        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);
+    }
+
+    @Test
+    public void sendTextForSubscriberTest_InteractAcrossUsers() {
+        int subId = 1;
+        // Sending text to subscriber should not fail when the caller has the
+        // INTERACT_ACROSS_USERS_FULL permission.
+        doReturn(false).when(mSubscriptionManager)
+                .isSubscriptionAssociatedWithUser(eq(subId), any());
+        doReturn(PackageManager.PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(
+                eq(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL));
+
+        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);
+    }
+
+    @Test
+    public void sendTextForSubscriberTestFail() {
+        int subId = 1;
+        // Sending text to subscriber should fail when the caller does not have the
+        // INTERACT_ACROSS_USERS_FULL permission and is not associated with the subscription.
+        doReturn(false).when(mSubscriptionManager)
+                .isSubscriptionAssociatedWithUser(eq(subId), any());
+        doReturn(PackageManager.PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(
+                eq(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL));
+
+        mSmsControllerUT.sendTextForSubscriber(subId, mCallingPackage, null, "1234",
+                null, "text", null, null, false, 0L, true, true);
+        verify(mIccSmsInterfaceManager, Mockito.times(0))
+                .sendText(mCallingPackage, "1234", null, "text", null, null, false, 0L, true);
+    }
 }
\ 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 6ef8508..b073cd4 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java
@@ -19,6 +19,8 @@
 import static com.android.internal.telephony.SmsResponse.NO_ERROR_CODE;
 
 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.Matchers.any;
@@ -28,13 +30,24 @@
 import static org.mockito.Mockito.doReturn;
 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 static org.mockito.Mockito.when;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.AsyncResult;
+import android.os.Looper;
 import android.os.Message;
 import android.provider.Telephony.Sms.Intents;
+import android.telephony.DisconnectCause;
+import android.telephony.DomainSelectionService;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.PhoneNumberUtils;
 import android.telephony.SmsManager;
 import android.test.FlakyTest;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -42,36 +55,140 @@
 import android.testing.TestableLooper;
 import android.util.Singleton;
 
+import com.android.ims.ImsManager;
+import com.android.internal.telephony.domainselection.DomainSelectionConnection;
+import com.android.internal.telephony.domainselection.EmergencySmsDomainSelectionConnection;
+import com.android.internal.telephony.domainselection.SmsDomainSelectionConnection;
+import com.android.internal.telephony.uicc.IccUtils;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
 
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.concurrent.CompletableFuture;
 
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class SmsDispatchersControllerTest extends TelephonyTest {
+    /**
+     * Inherits the SmsDispatchersController to verify the protected methods.
+     */
+    private static class TestSmsDispatchersController extends SmsDispatchersController {
+        TestSmsDispatchersController(Phone phone, SmsStorageMonitor storageMonitor,
+                SmsUsageMonitor usageMonitor, Looper looper) {
+            super(phone, storageMonitor, usageMonitor, looper);
+        }
+
+        public DomainSelectionConnectionHolder testGetDomainSelectionConnectionHolder(
+                boolean emergency) {
+            return getDomainSelectionConnectionHolder(emergency);
+        }
+
+        public void testSendData(String callingPackage, String destAddr, String scAddr,
+                int destPort, byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent,
+                boolean isForVvm) {
+            sendData(callingPackage, destAddr, scAddr,
+                    destPort, data, sentIntent, deliveryIntent, isForVvm);
+        }
+
+        public void testSendMultipartText(String destAddr, String scAddr,
+                ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
+                ArrayList<PendingIntent> deliveryIntents, Uri messageUri, String callingPkg,
+                boolean persistMessage, int priority, boolean expectMore, int validityPeriod,
+                long messageId) {
+            sendMultipartText(destAddr, scAddr, parts, sentIntents, deliveryIntents, messageUri,
+                    callingPkg, persistMessage, priority, expectMore, validityPeriod, messageId);
+        }
+    }
+
+    /**
+     * Inherits the SMSDispatcher to verify the abstract or protected methods.
+     */
+    protected abstract static class TestSmsDispatcher extends SMSDispatcher {
+        public TestSmsDispatcher(Phone phone, SmsDispatchersController smsDispatchersController) {
+            super(phone, smsDispatchersController);
+        }
+
+        @Override
+        public void sendData(String callingPackage, String destAddr, String scAddr, int destPort,
+                byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent,
+                boolean isForVvm) {
+            super.sendData(callingPackage, destAddr, scAddr, destPort,
+                    data, sentIntent, deliveryIntent, isForVvm);
+        }
+
+        @Override
+        public void sendSms(SmsTracker tracker) {
+        }
+
+        @Override
+        public String getFormat() {
+            return SmsConstants.FORMAT_3GPP;
+        }
+    }
+
+    /**
+     * Inherits the SMSDispatcher to verify the protected methods.
+     */
+    protected static class TestImsSmsDispatcher extends ImsSmsDispatcher {
+        public TestImsSmsDispatcher(Phone phone, SmsDispatchersController smsDispatchersController,
+                FeatureConnectorFactory factory) {
+            super(phone, smsDispatchersController, factory);
+        }
+
+        @Override
+        public void sendData(String callingPackage, String destAddr, String scAddr, int destPort,
+                byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent,
+                boolean isForVvm) {
+            super.sendData(callingPackage, destAddr, scAddr, destPort,
+                    data, sentIntent, deliveryIntent, isForVvm);
+        }
+
+        @Override
+        public String getFormat() {
+            return SmsConstants.FORMAT_3GPP;
+        }
+    }
+
+    private static final String ACTION_TEST_SMS_SENT = "TEST_SMS_SENT";
+
     // Mocked classes
     private SMSDispatcher.SmsTracker mTracker;
+    private PendingIntent mSentIntent;
+    private TestImsSmsDispatcher mImsSmsDispatcher;
+    private TestSmsDispatcher mGsmSmsDispatcher;
+    private TestSmsDispatcher mCdmaSmsDispatcher;
+    private SmsDomainSelectionConnection mSmsDsc;
+    private EmergencySmsDomainSelectionConnection mEmergencySmsDsc;
 
-    private SmsDispatchersController mSmsDispatchersController;
+    private TestSmsDispatchersController mSmsDispatchersController;
     private boolean mInjectionCallbackTriggered = false;
+    private CompletableFuture<Integer> mDscFuture;
 
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
         mTracker = mock(SMSDispatcher.SmsTracker.class);
         setupMockPackagePermissionChecks();
-
-        mSmsDispatchersController = new SmsDispatchersController(mPhone, mSmsStorageMonitor,
-            mSmsUsageMonitor);
+        mSmsDispatchersController = new TestSmsDispatchersController(mPhone, mSmsStorageMonitor,
+            mSmsUsageMonitor, mTestableLooper.getLooper());
+        setUpDomainSelectionConnectionAsNotSupported();
         processAllMessages();
     }
 
     @After
     public void tearDown() throws Exception {
+        mImsSmsDispatcher = null;
+        mGsmSmsDispatcher = null;
+        mCdmaSmsDispatcher = null;
+        mSmsDsc = null;
+        mEmergencySmsDsc = null;
+        mDscFuture = null;
         mSmsDispatchersController.dispose();
         mSmsDispatchersController = null;
         super.tearDown();
@@ -91,6 +208,30 @@
         assertTrue(mSmsDispatchersController.isIms());
     }
 
+    @Test @SmallTest
+    public void testReportSmsMemoryStatus() throws Exception {
+        int eventReportMemoryStatusDone = 3;
+        SmsStorageMonitor smsStorageMonnitor = new SmsStorageMonitor(mPhone);
+        Message result = smsStorageMonnitor.obtainMessage(eventReportMemoryStatusDone);
+        ImsSmsDispatcher mImsSmsDispatcher = Mockito.mock(ImsSmsDispatcher.class);
+        mSmsDispatchersController.setImsSmsDispatcher(mImsSmsDispatcher);
+        mSmsDispatchersController.reportSmsMemoryStatus(result);
+        AsyncResult ar = (AsyncResult) result.obj;
+        verify(mImsSmsDispatcher).onMemoryAvailable();
+        assertNull(ar.exception);
+    }
+
+    @Test @SmallTest
+    public void testReportSmsMemoryStatusFailure() throws Exception {
+        int eventReportMemoryStatusDone = 3;
+        SmsStorageMonitor smsStorageMonnitor = new SmsStorageMonitor(mPhone);
+        Message result = smsStorageMonnitor.obtainMessage(eventReportMemoryStatusDone);
+        mSmsDispatchersController.setImsSmsDispatcher(null);
+        mSmsDispatchersController.reportSmsMemoryStatus(result);
+        AsyncResult ar = (AsyncResult) result.obj;
+        assertNotNull(ar.exception);
+    }
+
     @Test @SmallTest @FlakyTest
     public void testSendImsGmsTest() throws Exception {
         switchImsSmsFormat(PhoneConstants.PHONE_TYPE_GSM);
@@ -179,6 +320,225 @@
         assertEquals(true, mInjectionCallbackTriggered);
     }
 
+    @Test @SmallTest
+    public void testSendImsGmsTestWithSmsc() {
+        IccSmsInterfaceManager iccSmsInterfaceManager = Mockito.mock(IccSmsInterfaceManager.class);
+        when(mPhone.getIccSmsInterfaceManager()).thenReturn(iccSmsInterfaceManager);
+        when(iccSmsInterfaceManager.getSmscAddressFromIccEf("com.android.messaging"))
+                .thenReturn("222");
+        switchImsSmsFormat(PhoneConstants.PHONE_TYPE_GSM);
+
+        mSmsDispatchersController.sendText("111", null /*scAddr*/, TAG,
+                null, null, null, "com.android.messaging",
+                false, -1, false, -1, false, 0L);
+        byte[] smscbyte = PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(
+                "222");
+        String smsc = IccUtils.bytesToHexString(smscbyte);
+        verify(mSimulatedCommandsVerifier).sendImsGsmSms(eq(smsc), anyString(),
+                anyInt(), anyInt(), any(Message.class));
+    }
+
+    @Test
+    @SmallTest
+    public void testSendDataWhenDomainPs() throws Exception {
+        sendDataWithDomainSelection(NetworkRegistrationInfo.DOMAIN_PS, false);
+    }
+
+    @Test
+    @SmallTest
+    public void testSendDataWhenDomainCsAndCdma() throws Exception {
+        when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_CDMA);
+        sendDataWithDomainSelection(NetworkRegistrationInfo.DOMAIN_PS, true);
+    }
+
+    @Test
+    @SmallTest
+    public void testSendDataWhenDomainCsAndGsm() throws Exception {
+        when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_GSM);
+        sendDataWithDomainSelection(NetworkRegistrationInfo.DOMAIN_PS, false);
+    }
+
+    @Test
+    @SmallTest
+    public void testSendTextWhenDomainPs() throws Exception {
+        sendTextWithDomainSelection(NetworkRegistrationInfo.DOMAIN_PS, false);
+    }
+
+    @Test
+    @SmallTest
+    public void testSendTextWhenDomainCsAndCdma() throws Exception {
+        when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_CDMA);
+        sendTextWithDomainSelection(NetworkRegistrationInfo.DOMAIN_PS, true);
+    }
+
+    @Test
+    @SmallTest
+    public void testSendTextWhenDomainCsAndGsm() throws Exception {
+        when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_GSM);
+        sendTextWithDomainSelection(NetworkRegistrationInfo.DOMAIN_PS, false);
+    }
+
+    @Test
+    @SmallTest
+    public void testSendMultipartTextWhenDomainPs() throws Exception {
+        sendMultipartTextWithDomainSelection(NetworkRegistrationInfo.DOMAIN_PS, false);
+    }
+
+    @Test
+    @SmallTest
+    public void testSendMultipartTextWhenDomainCsAndCdma() throws Exception {
+        when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_CDMA);
+        sendMultipartTextWithDomainSelection(NetworkRegistrationInfo.DOMAIN_PS, true);
+    }
+
+    @Test
+    @SmallTest
+    public void testSendMultipartTextWhenDomainCsAndGsm() throws Exception {
+        when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_GSM);
+        sendMultipartTextWithDomainSelection(NetworkRegistrationInfo.DOMAIN_PS, false);
+    }
+
+    @Test
+    @SmallTest
+    public void testSendRetrySmsWhenDomainPs() throws Exception {
+        sendRetrySmsWithDomainSelection(NetworkRegistrationInfo.DOMAIN_PS,
+                PhoneConstants.PHONE_TYPE_GSM, SmsConstants.FORMAT_3GPP);
+    }
+
+    @Test
+    @SmallTest
+    public void testSendRetrySmsWhenDomainCsAndCdma() throws Exception {
+        sendRetrySmsWithDomainSelection(NetworkRegistrationInfo.DOMAIN_CS,
+                PhoneConstants.PHONE_TYPE_CDMA, SmsConstants.FORMAT_3GPP2);
+    }
+
+    @Test
+    @SmallTest
+    public void testSendRetrySmsWhenDomainCsAndGsm() throws Exception {
+        sendRetrySmsWithDomainSelection(NetworkRegistrationInfo.DOMAIN_CS,
+                PhoneConstants.PHONE_TYPE_GSM, SmsConstants.FORMAT_3GPP);
+    }
+
+    @Test
+    @SmallTest
+    public void testSendRetrySmsWhenImsAlreadyUsedAndCdma() throws Exception {
+        sendRetrySmsWhenImsAlreadyUsed(PhoneConstants.PHONE_TYPE_CDMA, SmsConstants.FORMAT_3GPP2);
+    }
+
+    @Test
+    @SmallTest
+    public void testSendRetrySmsWhenImsAlreadyUsedAndGsm() throws Exception {
+        sendRetrySmsWhenImsAlreadyUsed(PhoneConstants.PHONE_TYPE_GSM, SmsConstants.FORMAT_3GPP);
+    }
+
+    @Test
+    @SmallTest
+    public void testSendEmergencyTextWhenDomainPs() throws Exception {
+        setUpDomainSelectionConnection();
+        setUpSmsDispatchers();
+
+        mSmsDispatchersController.sendText("911", "2222", "text", mSentIntent, null, null,
+                "test-app", false, 0, false, 10, false, 1L, false);
+
+        SmsDispatchersController.DomainSelectionConnectionHolder holder =
+                mSmsDispatchersController.testGetDomainSelectionConnectionHolder(true);
+        verify(mEmergencySmsDsc).requestDomainSelection(any(), any());
+        assertNotNull(holder);
+        assertNotNull(holder.getConnection());
+        assertTrue(holder.isEmergency());
+        assertTrue(holder.isDomainSelectionRequested());
+        assertEquals(1, holder.getPendingRequests().size());
+
+        mDscFuture.complete(NetworkRegistrationInfo.DOMAIN_PS);
+        processAllMessages();
+
+        verify(mEmergencySmsDsc).finishSelection();
+        verify(mImsSmsDispatcher).sendText(eq("911"), eq("2222"), eq("text"), eq(mSentIntent),
+                any(), any(), eq("test-app"), eq(false), eq(0), eq(false), eq(10), eq(false),
+                eq(1L), eq(false));
+        assertNull(holder.getConnection());
+        assertFalse(holder.isDomainSelectionRequested());
+        assertEquals(0, holder.getPendingRequests().size());
+    }
+
+    @Test
+    @SmallTest
+    public void testNotifyDomainSelectionTerminated() throws Exception {
+        setUpDomainSelectionConnection();
+        setUpSmsDispatchers();
+
+        mSmsDispatchersController.sendText("1111", "2222", "text", mSentIntent, null, null,
+                "test-app", false, 0, false, 10, false, 1L, false);
+
+        SmsDispatchersController.DomainSelectionConnectionHolder holder =
+                mSmsDispatchersController.testGetDomainSelectionConnectionHolder(false);
+        ArgumentCaptor<DomainSelectionConnection.DomainSelectionConnectionCallback> captor =
+                ArgumentCaptor.forClass(
+                        DomainSelectionConnection.DomainSelectionConnectionCallback.class);
+        verify(mSmsDsc).requestDomainSelection(any(), captor.capture());
+        assertNotNull(holder);
+        assertNotNull(holder.getConnection());
+        assertTrue(holder.isDomainSelectionRequested());
+        assertEquals(1, holder.getPendingRequests().size());
+
+        DomainSelectionConnection.DomainSelectionConnectionCallback callback = captor.getValue();
+        assertNotNull(callback);
+
+        mSmsDispatchersController.post(() -> {
+            callback.onSelectionTerminated(DisconnectCause.LOCAL);
+        });
+        processAllMessages();
+
+        verify(mSmsDsc, never()).finishSelection();
+        assertNull(holder.getConnection());
+        assertFalse(holder.isDomainSelectionRequested());
+        assertEquals(0, holder.getPendingRequests().size());
+
+        // We can use the IntentReceiver for receiving the sent result, but it can be reported as
+        // a flaky test since sometimes broadcasts can take a long time if the system is under load.
+        // At this point, we couldn't use the PendingIntent as a mock because it's a final class
+        // so this test checks the method in the IActivityManager when the PendingIntent#send(int)
+        // is called.
+        verify(mIActivityManager).sendIntentSender(any(), any(), any(),
+                eq(SmsManager.RESULT_ERROR_GENERIC_FAILURE), any(), any(), any(), any(), any());
+    }
+
+    @Test
+    @SmallTest
+    public void testSendTextContinuously() throws Exception {
+        setUpDomainSelectionConnection();
+        setUpSmsDispatchers();
+
+        mSmsDispatchersController.sendText("1111", "2222", "text", mSentIntent, null, null,
+                "test-app", false, 0, false, 10, false, 1L, false);
+
+        SmsDispatchersController.DomainSelectionConnectionHolder holder =
+                mSmsDispatchersController.testGetDomainSelectionConnectionHolder(false);
+        assertNotNull(holder);
+        assertNotNull(holder.getConnection());
+        assertTrue(holder.isDomainSelectionRequested());
+        assertEquals(1, holder.getPendingRequests().size());
+
+        mSmsDispatchersController.sendText("1111", "2222", "text", mSentIntent, null, null,
+                "test-app", false, 0, false, 10, false, 1L, false);
+
+        verify(mSmsDsc).requestDomainSelection(any(), any());
+        assertNotNull(holder.getConnection());
+        assertTrue(holder.isDomainSelectionRequested());
+        assertEquals(2, holder.getPendingRequests().size());
+
+        mDscFuture.complete(NetworkRegistrationInfo.DOMAIN_PS);
+        processAllMessages();
+
+        verify(mSmsDsc).finishSelection();
+        verify(mImsSmsDispatcher, times(2)).sendText(eq("1111"), eq("2222"), eq("text"),
+                eq(mSentIntent), any(), any(), eq("test-app"), eq(false), eq(0), eq(false), eq(10),
+                eq(false), eq(1L), eq(false));
+        assertNull(holder.getConnection());
+        assertFalse(holder.isDomainSelectionRequested());
+        assertEquals(0, holder.getPendingRequests().size());
+    }
+
     private void switchImsSmsFormat(int phoneType) {
         mSimulatedCommands.setImsRegistrationState(new int[]{1, phoneType});
         mSimulatedCommands.notifyImsNetworkStateChanged();
@@ -186,4 +546,249 @@
         processAllMessages();
         assertTrue(mSmsDispatchersController.isIms());
     }
+
+    @Test
+    public void testSetImsManager() {
+        ImsManager imsManager = mock(ImsManager.class);
+        assertTrue(mSmsDispatchersController.setImsManager(imsManager));
+    }
+
+    private void setUpDomainSelectionConnectionAsNotSupported() {
+        mSmsDispatchersController.setDomainSelectionResolverProxy(
+                new SmsDispatchersController.DomainSelectionResolverProxy() {
+                    @Override
+                    @Nullable
+                    public DomainSelectionConnection getDomainSelectionConnection(Phone phone,
+                            @DomainSelectionService.SelectorType int selectorType,
+                            boolean isEmergency) {
+                        return null;
+                    }
+
+                    @Override
+                    public boolean isDomainSelectionSupported() {
+                        return false;
+                    }
+                });
+    }
+
+    private void setUpDomainSelectionConnection()  {
+        mEmergencySmsDsc = Mockito.mock(EmergencySmsDomainSelectionConnection.class);
+        mSmsDsc = Mockito.mock(SmsDomainSelectionConnection.class);
+        mSmsDispatchersController.setDomainSelectionResolverProxy(
+                new SmsDispatchersController.DomainSelectionResolverProxy() {
+                    @Override
+                    @Nullable
+                    public DomainSelectionConnection getDomainSelectionConnection(Phone phone,
+                            @DomainSelectionService.SelectorType int selectorType,
+                            boolean isEmergency) {
+                        return isEmergency ? mEmergencySmsDsc : mSmsDsc;
+                    }
+
+                    @Override
+                    public boolean isDomainSelectionSupported() {
+                        return true;
+                    }
+                });
+
+        mDscFuture = new CompletableFuture<>();
+        when(mSmsDsc.requestDomainSelection(
+                any(DomainSelectionService.SelectionAttributes.class),
+                any(DomainSelectionConnection.DomainSelectionConnectionCallback.class)))
+                .thenReturn(mDscFuture);
+        when(mEmergencySmsDsc.requestDomainSelection(
+                any(DomainSelectionService.SelectionAttributes.class),
+                any(DomainSelectionConnection.DomainSelectionConnectionCallback.class)))
+                .thenReturn(mDscFuture);
+    }
+
+    private void setUpSmsDispatchers() throws Exception {
+        mImsSmsDispatcher = Mockito.mock(TestImsSmsDispatcher.class);
+        mGsmSmsDispatcher = Mockito.mock(TestSmsDispatcher.class);
+        mCdmaSmsDispatcher = Mockito.mock(TestSmsDispatcher.class);
+
+        replaceInstance(SmsDispatchersController.class, "mImsSmsDispatcher",
+                mSmsDispatchersController, mImsSmsDispatcher);
+        replaceInstance(SmsDispatchersController.class, "mGsmDispatcher",
+                mSmsDispatchersController, mGsmSmsDispatcher);
+        replaceInstance(SmsDispatchersController.class, "mCdmaDispatcher",
+                mSmsDispatchersController, mCdmaSmsDispatcher);
+
+        when(mTelephonyManager.isEmergencyNumber(eq("911"))).thenReturn(true);
+
+        mSentIntent = PendingIntent.getBroadcast(TestApplication.getAppContext(), 0,
+                new Intent(ACTION_TEST_SMS_SENT), PendingIntent.FLAG_MUTABLE
+                        | PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT);
+    }
+
+    private void sendDataWithDomainSelection(@NetworkRegistrationInfo.Domain int domain,
+            boolean isCdmaMo) throws Exception {
+        setUpDomainSelectionConnection();
+        setUpSmsDispatchers();
+
+        byte[] data = new byte[] { 0x01 };
+        mSmsDispatchersController.testSendData(
+                "test-app", "1111", "2222", 8080, data, mSentIntent, null, false);
+
+        SmsDispatchersController.DomainSelectionConnectionHolder holder =
+                mSmsDispatchersController.testGetDomainSelectionConnectionHolder(false);
+        verify(mSmsDsc).requestDomainSelection(any(), any());
+        assertNotNull(holder);
+        assertNotNull(holder.getConnection());
+        assertTrue(holder.isDomainSelectionRequested());
+        assertEquals(1, holder.getPendingRequests().size());
+
+        mDscFuture.complete(domain);
+        processAllMessages();
+
+        verify(mSmsDsc).finishSelection();
+        if (domain == NetworkRegistrationInfo.DOMAIN_PS) {
+            verify(mImsSmsDispatcher).sendData(eq("test-app"), eq("1111"), eq("2222"), eq(8080),
+                    eq(data), eq(mSentIntent), any(), eq(false));
+        } else if (isCdmaMo) {
+            verify(mCdmaSmsDispatcher).sendData(eq("test-app"), eq("1111"), eq("2222"), eq(8080),
+                    eq(data), eq(mSentIntent), any(), eq(false));
+        } else {
+            verify(mGsmSmsDispatcher).sendData(eq("test-app"), eq("1111"), eq("2222"), eq(8080),
+                    eq(data), eq(mSentIntent), any(), eq(false));
+        }
+        assertNull(holder.getConnection());
+        assertFalse(holder.isDomainSelectionRequested());
+        assertEquals(0, holder.getPendingRequests().size());
+    }
+
+    private void sendTextWithDomainSelection(@NetworkRegistrationInfo.Domain int domain,
+            boolean isCdmaMo) throws Exception {
+        setUpDomainSelectionConnection();
+        setUpSmsDispatchers();
+
+        mSmsDispatchersController.sendText("1111", "2222", "text", mSentIntent, null, null,
+                "test-app", false, 0, false, 10, false, 1L, false);
+
+        SmsDispatchersController.DomainSelectionConnectionHolder holder =
+                mSmsDispatchersController.testGetDomainSelectionConnectionHolder(false);
+        verify(mSmsDsc).requestDomainSelection(any(), any());
+        assertNotNull(holder);
+        assertNotNull(holder.getConnection());
+        assertTrue(holder.isDomainSelectionRequested());
+        assertEquals(1, holder.getPendingRequests().size());
+
+        mDscFuture.complete(domain);
+        processAllMessages();
+
+        verify(mSmsDsc).finishSelection();
+        if (domain == NetworkRegistrationInfo.DOMAIN_PS) {
+            verify(mImsSmsDispatcher).sendText(eq("1111"), eq("2222"), eq("text"), eq(mSentIntent),
+                    any(), any(), eq("test-app"), eq(false), eq(0), eq(false), eq(10), eq(false),
+                    eq(1L), eq(false));
+        } else if (isCdmaMo) {
+            verify(mCdmaSmsDispatcher).sendText(eq("1111"), eq("2222"), eq("text"), eq(mSentIntent),
+                    any(), any(), eq("test-app"), eq(false), eq(0), eq(false), eq(10), eq(false),
+                    eq(1L), eq(false));
+        } else {
+            verify(mGsmSmsDispatcher).sendText(eq("1111"), eq("2222"), eq("text"), eq(mSentIntent),
+                    any(), any(), eq("test-app"), eq(false), eq(0), eq(false), eq(10), eq(false),
+                    eq(1L), eq(false));
+        }
+        assertNull(holder.getConnection());
+        assertFalse(holder.isDomainSelectionRequested());
+        assertEquals(0, holder.getPendingRequests().size());
+    }
+
+    private void sendMultipartTextWithDomainSelection(@NetworkRegistrationInfo.Domain int domain,
+            boolean isCdmaMo) throws Exception {
+        setUpDomainSelectionConnection();
+        setUpSmsDispatchers();
+
+        ArrayList<String> parts = new ArrayList<>();
+        ArrayList<PendingIntent> sentIntents = new ArrayList<>();
+        ArrayList<PendingIntent> deliveryIntents = new ArrayList<>();
+        mSmsDispatchersController.testSendMultipartText("1111", "2222", parts, sentIntents,
+                deliveryIntents, null, "test-app", false, 0, false, 10, 1L);
+
+        SmsDispatchersController.DomainSelectionConnectionHolder holder =
+                mSmsDispatchersController.testGetDomainSelectionConnectionHolder(false);
+        verify(mSmsDsc).requestDomainSelection(any(), any());
+        assertNotNull(holder);
+        assertNotNull(holder.getConnection());
+        assertTrue(holder.isDomainSelectionRequested());
+        assertEquals(1, holder.getPendingRequests().size());
+
+        mDscFuture.complete(domain);
+        processAllMessages();
+
+        verify(mSmsDsc).finishSelection();
+        if (domain == NetworkRegistrationInfo.DOMAIN_PS) {
+            verify(mImsSmsDispatcher).sendMultipartText(eq("1111"), eq("2222"), eq(parts),
+                    eq(sentIntents), eq(deliveryIntents), any(), eq("test-app"), eq(false), eq(0),
+                    eq(false), eq(10), eq(1L));
+        } else if (isCdmaMo) {
+            verify(mCdmaSmsDispatcher).sendMultipartText(eq("1111"), eq("2222"), eq(parts),
+                    eq(sentIntents), eq(deliveryIntents), any(), eq("test-app"), eq(false), eq(0),
+                    eq(false), eq(10), eq(1L));
+        } else {
+            verify(mGsmSmsDispatcher).sendMultipartText(eq("1111"), eq("2222"), eq(parts),
+                    eq(sentIntents), eq(deliveryIntents), any(), eq("test-app"), eq(false), eq(0),
+                    eq(false), eq(10), eq(1L));
+        }
+        assertNull(holder.getConnection());
+        assertFalse(holder.isDomainSelectionRequested());
+        assertEquals(0, holder.getPendingRequests().size());
+    }
+
+    private void sendRetrySmsWithDomainSelection(@NetworkRegistrationInfo.Domain int domain,
+            int phoneType, String smsFormat) throws Exception {
+        setUpDomainSelectionConnection();
+        setUpSmsDispatchers();
+        when(mPhone.getPhoneType()).thenReturn(phoneType);
+        when(mImsSmsDispatcher.getFormat()).thenReturn(SmsConstants.FORMAT_3GPP);
+        when(mCdmaSmsDispatcher.getFormat()).thenReturn(SmsConstants.FORMAT_3GPP2);
+        when(mGsmSmsDispatcher.getFormat()).thenReturn(SmsConstants.FORMAT_3GPP);
+        replaceInstance(SMSDispatcher.SmsTracker.class, "mFormat", mTracker, smsFormat);
+
+        mSmsDispatchersController.sendRetrySms(mTracker);
+
+        SmsDispatchersController.DomainSelectionConnectionHolder holder =
+                mSmsDispatchersController.testGetDomainSelectionConnectionHolder(false);
+        verify(mSmsDsc).requestDomainSelection(any(), any());
+        assertNotNull(holder);
+        assertNotNull(holder.getConnection());
+        assertTrue(holder.isDomainSelectionRequested());
+        assertEquals(1, holder.getPendingRequests().size());
+
+        mDscFuture.complete(domain);
+        processAllMessages();
+
+        verify(mSmsDsc).finishSelection();
+        if (domain == NetworkRegistrationInfo.DOMAIN_PS) {
+            verify(mImsSmsDispatcher).sendSms(eq(mTracker));
+        } else if (SmsConstants.FORMAT_3GPP2.equals(smsFormat)) {
+            verify(mCdmaSmsDispatcher).sendSms(eq(mTracker));
+        } else {
+            verify(mGsmSmsDispatcher).sendSms(eq(mTracker));
+        }
+        assertNull(holder.getConnection());
+        assertFalse(holder.isDomainSelectionRequested());
+        assertEquals(0, holder.getPendingRequests().size());
+    }
+
+    private void sendRetrySmsWhenImsAlreadyUsed(int phoneType, String smsFormat) throws Exception {
+        setUpDomainSelectionConnection();
+        setUpSmsDispatchers();
+        when(mPhone.getPhoneType()).thenReturn(phoneType);
+        when(mImsSmsDispatcher.getFormat()).thenReturn(SmsConstants.FORMAT_3GPP);
+        when(mCdmaSmsDispatcher.getFormat()).thenReturn(SmsConstants.FORMAT_3GPP2);
+        when(mGsmSmsDispatcher.getFormat()).thenReturn(SmsConstants.FORMAT_3GPP);
+        replaceInstance(SMSDispatcher.SmsTracker.class, "mFormat", mTracker, smsFormat);
+        mTracker.mUsesImsServiceForIms = true;
+
+        mSmsDispatchersController.sendRetrySms(mTracker);
+
+        verify(mSmsDsc, never()).requestDomainSelection(any(), any());
+
+        if (SmsConstants.FORMAT_3GPP2.equals(smsFormat)) {
+            verify(mCdmaSmsDispatcher).sendSms(eq(mTracker));
+        } else {
+            verify(mGsmSmsDispatcher).sendSms(eq(mTracker));
+        }
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SmsPermissionsTest.java b/tests/telephonytests/src/com/android/internal/telephony/SmsPermissionsTest.java
index 2a7f35b..5057eea 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SmsPermissionsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SmsPermissionsTest.java
@@ -78,7 +78,7 @@
                 }
 
                 @Override
-                public boolean isCallerDefaultSmsPackage(String packageName) {
+                public boolean isCallerDefaultSmsPackage(String packageName, int uid) {
                     return mCallerIsDefaultSmsPackage;
                 }
             };
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SmsStorageMonitorTest.java b/tests/telephonytests/src/com/android/internal/telephony/SmsStorageMonitorTest.java
index 3eace63..b6775eb 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SmsStorageMonitorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SmsStorageMonitorTest.java
@@ -20,8 +20,11 @@
 
 import static org.junit.Assert.*;
 import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
 
 import android.content.Intent;
+import android.content.res.Resources;
 import android.os.Message;
 import android.provider.Telephony;
 import android.test.suitebuilder.annotation.MediumTest;
@@ -34,6 +37,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
 
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -138,6 +143,41 @@
     }
 
     @Test @SmallTest
+    public void testReportSmsMemoryStatusToIms() {
+        Resources mockResources = Mockito.mock(Resources.class);
+        doReturn(mockResources).when(mContext).getResources();
+        doReturn(true).when(mockResources).getBoolean(anyInt());
+        doReturn(true).when(mIccSmsInterfaceManager.mDispatchersController).isIms();
+
+        mSimulatedCommands.notifyRadioOn();
+        processAllMessages();
+
+        verify(mSimulatedCommandsVerifier, never()).reportSmsMemoryStatus(anyBoolean(),
+                any(Message.class));
+
+        // Send DEVICE_STORAGE_FULL
+        mContextFixture.getTestDouble().sendBroadcast(
+                new Intent(Intent.ACTION_DEVICE_STORAGE_FULL));
+        processAllMessages();
+
+        verify(mSimulatedCommandsVerifier).reportSmsMemoryStatus(eq(false), any(Message.class));
+        assertFalse(mSmsStorageMonitor.isStorageAvailable());
+
+        mSimulatedCommands.notifyRadioOn();
+        processAllMessages();
+
+        verify(mSimulatedCommandsVerifier).reportSmsMemoryStatus(eq(false), any(Message.class));
+
+        // Send DEVICE_STORAGE_NOT_FULL
+        mContextFixture.getTestDouble().sendBroadcast(
+                new Intent(Intent.ACTION_DEVICE_STORAGE_NOT_FULL));
+        processAllMessages();
+
+        verify(mIccSmsInterfaceManager.mDispatchersController)
+                .reportSmsMemoryStatus(any(Message.class));
+    }
+
+    @Test @SmallTest
     public void testReportSmsMemoryStatusDuringRetry() {
         mSimulatedCommands.setReportSmsMemoryStatusFailResponse(true);
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
deleted file mode 100644
index 2f8b1bb..0000000
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
+++ /dev/null
@@ -1,2302 +0,0 @@
-/*
- * Copyright (C) 2016 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 android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION;
-
-import static com.android.internal.telephony.SubscriptionController.REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID;
-import static com.android.internal.telephony.uicc.IccCardStatus.CardState.CARDSTATE_PRESENT;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThrows;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.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.Manifest;
-import android.compat.testing.PlatformCompatChangeRule;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.ParcelUuid;
-import android.os.PersistableBundle;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.telephony.CarrierConfigManager;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.telephony.UiccPortInfo;
-import android.telephony.UiccSlotInfo;
-import android.test.mock.MockContentResolver;
-
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.telephony.data.PhoneSwitcher;
-import com.android.internal.telephony.uicc.IccCardStatus;
-import com.android.internal.telephony.uicc.UiccController;
-import com.android.internal.telephony.uicc.UiccSlot;
-
-import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
-import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.mockito.ArgumentCaptor;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-public class SubscriptionControllerTest extends TelephonyTest {
-    private static final int SINGLE_SIM = 1;
-    private static final int DUAL_SIM = 2;
-    private static final int FAKE_SUBID = 123;
-    private String mCallingPackage;
-    private String mCallingFeature;
-    private SubscriptionController mSubscriptionControllerUT;
-    private MockContentResolver mMockContentResolver;
-    private FakeTelephonyProvider mFakeTelephonyProvider;
-    private PersistableBundle mCarrierConfigs;
-
-    // Mocked classes
-    private UiccSlot mUiccSlot;
-    private ITelephonyRegistry.Stub mTelephonyRegistryMock;
-    private MultiSimSettingController mMultiSimSettingControllerMock;
-    private ISetOpportunisticDataCallback mSetOpptDataCallback;
-    private Handler mHandler;
-    private SubscriptionInfo mMockSubscriptionInfo;
-
-    private static final String MAC_ADDRESS_PREFIX = "mac_";
-    private static final String DISPLAY_NAME_PREFIX = "my_phone_";
-
-    private static final String UNAVAILABLE_ICCID = "";
-    private static final String UNAVAILABLE_NUMBER = "";
-    private static final String DISPLAY_NUMBER = "123456";
-    private static final String DISPLAY_NAME = "testing_display_name";
-
-    @Rule
-    public TestRule mCompatChangeRule = new PlatformCompatChangeRule();
-
-    @Before
-    public void setUp() throws Exception {
-        super.setUp(getClass().getSimpleName());
-        enableSubscriptionManagerService(false);
-        mUiccSlot = mock(UiccSlot.class);
-        mTelephonyRegistryMock = mock(ITelephonyRegistry.Stub.class);
-        mMultiSimSettingControllerMock = mock(MultiSimSettingController.class);
-        mSetOpptDataCallback = mock(ISetOpportunisticDataCallback.class);
-        mHandler = mock(Handler.class);
-        mMockSubscriptionInfo = mock(SubscriptionInfo.class);
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-        }
-
-        doReturn(SINGLE_SIM).when(mTelephonyManager).getSimCount();
-        doReturn(SINGLE_SIM).when(mTelephonyManager).getPhoneCount();
-        mMockContentResolver = (MockContentResolver) mContext.getContentResolver();
-        mFakeTelephonyProvider = new FakeTelephonyProvider();
-        mMockContentResolver.addProvider(SubscriptionManager.CONTENT_URI.getAuthority(),
-                mFakeTelephonyProvider);
-        replaceInstance(SubscriptionController.class, "sInstance", null, null);
-        replaceInstance(MultiSimSettingController.class, "sInstance", null,
-                mMultiSimSettingControllerMock);
-
-        mSubscriptionControllerUT = SubscriptionController.init(mContext);
-        mCallingPackage = mContext.getOpPackageName();
-        mCallingFeature = mContext.getAttributionTag();
-
-        doReturn(1).when(mProxyController).getMaxRafSupported();
-
-        // Carrier Config
-        mCarrierConfigs = mContextFixture.getCarrierConfigBundle();
-
-        mContextFixture.putIntArrayResource(com.android.internal.R.array.sim_colors, new int[]{5});
-        setupMocksForTelephonyPermissions(Build.VERSION_CODES.R);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        mContextFixture.addCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
-        /* Should clear fake content provider and resolver here */
-        mContext.getContentResolver().delete(SubscriptionManager.CONTENT_URI, null, null);
-
-        /* Clear sub info in mSubscriptionControllerUT since they will otherwise be persistent
-         * between each test case. */
-        if (mSubscriptionControllerUT != null) {
-            mSubscriptionControllerUT.clearSubInfo();
-            mSubscriptionControllerUT = null;
-        }
-
-        /* Clear settings for default voice/data/sms sub ID */
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION,
-                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION,
-                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION,
-                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-
-        mCallingPackage = null;
-        mCallingFeature = null;
-        mMockContentResolver = null;
-        mFakeTelephonyProvider = null;
-        mCarrierConfigs = null;
-        super.tearDown();
-    }
-
-    @Test @SmallTest
-    public void testInsertSim() {
-        //verify there is no sim inserted in the SubscriptionManager
-        assertEquals(0, mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage,
-                mCallingFeature));
-
-        int slotID = 0;
-        //insert one Subscription Info
-        mSubscriptionControllerUT.addSubInfo("test", null, slotID,
-                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
-
-        //verify there is one sim
-        assertEquals(1, mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage,
-                mCallingFeature));
-
-        //sanity for slot id and sub id
-        List<SubscriptionInfo> mSubList = mSubscriptionControllerUT
-                .getActiveSubscriptionInfoList(mCallingPackage, mCallingFeature);
-        assertTrue(mSubList != null && mSubList.size() > 0);
-        for (int i = 0; i < mSubList.size(); i++) {
-            assertTrue(SubscriptionManager.isValidSubscriptionId(
-                    mSubList.get(i).getSubscriptionId()));
-            assertTrue(SubscriptionManager.isValidSlotIndex(mSubList.get(i).getSimSlotIndex()));
-        }
-    }
-
-    @Test @SmallTest
-    public void testUsageSettingProperty() {
-        testInsertSim();
-        /* Get SUB ID */
-        int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false);
-        assertTrue(subIds != null && subIds.length != 0);
-        final int subId = subIds[0];
-
-        /* Getting, there is no direct getter function for each fields of property */
-        SubscriptionInfo subInfo = mSubscriptionControllerUT
-                .getActiveSubscriptionInfo(subId, mCallingPackage, mCallingFeature);
-
-        // assertEquals(SubscriptionManager.USAGE_SETTING_UNKNOWN, subInfo.getUsageSetting());
-
-        assertThrows(IllegalArgumentException.class,
-                () -> mSubscriptionControllerUT.setUsageSetting(
-                        SubscriptionManager.USAGE_SETTING_UNKNOWN,
-                        subId,
-                        mCallingPackage));
-
-        assertThrows(IllegalArgumentException.class,
-                () -> mSubscriptionControllerUT.setUsageSetting(
-                        SubscriptionManager.USAGE_SETTING_DEFAULT,
-                        SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
-                        mCallingPackage));
-
-        mSubscriptionControllerUT.setUsageSetting(
-                SubscriptionManager.USAGE_SETTING_DATA_CENTRIC,
-                subId,
-                mCallingPackage);
-
-        subInfo = mSubscriptionControllerUT
-                .getActiveSubscriptionInfo(subId, mCallingPackage, mCallingFeature);
-        assertEquals(SubscriptionManager.USAGE_SETTING_DATA_CENTRIC, subInfo.getUsageSetting());
-    }
-
-    @Test @SmallTest
-    public void testChangeSIMProperty() {
-        int dataRoaming = 1;
-        int iconTint = 1;
-        String disName = "TESTING";
-        String disNum = "12345";
-        boolean isOpportunistic = true;
-
-        testInsertSim();
-        /* Get SUB ID */
-        int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false);
-        assertTrue(subIds != null && subIds.length != 0);
-        int subID = subIds[0];
-
-        /* Getting, there is no direct getter function for each fields of property */
-        SubscriptionInfo subInfo = mSubscriptionControllerUT
-                .getActiveSubscriptionInfo(subID, mCallingPackage, mCallingFeature);
-
-        /* Setting */
-        mSubscriptionControllerUT.setDisplayNameUsingSrc(disName, subID,
-                SubscriptionManager.NAME_SOURCE_USER_INPUT);
-        mSubscriptionControllerUT.setDataRoaming(dataRoaming, subID);
-        mSubscriptionControllerUT.setDisplayNumber(disNum, subID);
-        mSubscriptionControllerUT.setIconTint(iconTint, subID);
-        mSubscriptionControllerUT.setOpportunistic(isOpportunistic, subID, mCallingPackage);
-
-        subInfo = mSubscriptionControllerUT
-            .getActiveSubscriptionInfo(subID, mCallingPackage, mCallingFeature);
-
-        assertNotNull(subInfo);
-        assertEquals(dataRoaming, subInfo.getDataRoaming());
-        assertEquals(disName, subInfo.getDisplayName());
-        assertEquals(iconTint, subInfo.getIconTint());
-        assertEquals(disNum, subInfo.getNumber());
-        assertEquals(isOpportunistic, subInfo.isOpportunistic());
-    }
-
-    @Test @SmallTest
-    public void testSetGetDisplayNameSrc() {
-        testInsertSim();
-
-        /* Get SUB ID */
-        int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false);
-        assertTrue(subIds != null && subIds.length != 0);
-        int subID = subIds[0];
-
-        /* Setting */
-        String disName = "TESTING";
-        int nameSource = SubscriptionManager.NAME_SOURCE_SIM_SPN;
-        mSubscriptionControllerUT.setDisplayNameUsingSrc(disName, subID, nameSource);
-        SubscriptionInfo subInfo = mSubscriptionControllerUT
-                .getActiveSubscriptionInfo(subID, mCallingPackage, mCallingFeature);
-        assertNotNull(subInfo);
-        assertEquals(disName, subInfo.getDisplayName());
-        assertEquals(nameSource, subInfo.getDisplayNameSource());
-    }
-
-    private void setSimEmbedded(boolean isEmbedded) throws Exception {
-        ContentValues values = new ContentValues();
-        values.put(SubscriptionManager.IS_EMBEDDED, isEmbedded ? 1 : 0);
-        mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values,
-                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + getFirstSubId(),
-                null);
-    }
-
-    @Test @SmallTest
-    public void testSetGetDisplayNameSrc_updateNameSourceCarrierWithEmbeddedSim()
-            throws Exception {
-        testInsertSim();
-
-        // Set values of DB
-        setSimEmbedded(true);
-        int subId = getFirstSubId();
-        int nameSource = SubscriptionManager.NAME_SOURCE_CARRIER;
-        mSubscriptionControllerUT.setDisplayNameUsingSrc(DISPLAY_NAME, subId, nameSource);
-
-        // Update with new value
-        String newDisplayName = "display_name_pnn";
-        int newNameSource = SubscriptionManager.NAME_SOURCE_SIM_PNN;
-
-        // Save to DB after updated
-        mSubscriptionControllerUT.setDisplayNameUsingSrc(newDisplayName, subId, newNameSource);
-
-        SubscriptionInfo subInfo = mSubscriptionControllerUT
-                .getActiveSubscriptionInfo(subId, mCallingPackage, mCallingFeature);
-
-        assertNotNull(subInfo);
-        assertEquals(DISPLAY_NAME, subInfo.getDisplayName());
-        assertEquals(nameSource, subInfo.getDisplayNameSource());
-    }
-
-    @Test @SmallTest
-    public void testSetGetDisplayNameSrc_updateNameSourceCarrierWithConfigIsNull()
-            throws Exception {
-        testInsertSim();
-
-        // Set values of DB
-        setSimEmbedded(false);
-        int subId = getFirstSubId();
-        int nameSource = SubscriptionManager.NAME_SOURCE_CARRIER;
-        mSubscriptionControllerUT.setDisplayNameUsingSrc(DISPLAY_NAME, subId, nameSource);
-
-        // Update with new value
-        String newDisplayName = "display_name_spn";
-        int newNameSource = SubscriptionManager.NAME_SOURCE_SIM_SPN;
-        when(mCarrierConfigManager.getConfigForSubId(subId)).thenReturn(null);
-
-        // Save to DB after updated
-        mSubscriptionControllerUT.setDisplayNameUsingSrc(newDisplayName, subId, newNameSource);
-
-        SubscriptionInfo subInfo = mSubscriptionControllerUT
-                .getActiveSubscriptionInfo(subId, mCallingPackage, mCallingFeature);
-
-        assertNotNull(subInfo);
-        assertEquals(DISPLAY_NAME, subInfo.getDisplayName());
-        assertEquals(nameSource, subInfo.getDisplayNameSource());
-    }
-
-    @Test @SmallTest
-    public void testSetGetDisplayNameSrc_updateNameSourceCarrierWithCarrierNameOverride()
-            throws Exception {
-        testInsertSim();
-
-        // Set values of DB
-        setSimEmbedded(false);
-        int subId = getFirstSubId();
-        int nameSource = SubscriptionManager.NAME_SOURCE_CARRIER;
-        mSubscriptionControllerUT.setDisplayNameUsingSrc(DISPLAY_NAME, subId, nameSource);
-
-        // Update with new value
-        int newNameSource = SubscriptionManager.NAME_SOURCE_SIM_SPN;
-        String newDisplayName = "display_name_spn";
-        mCarrierConfigs.putBoolean(CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL, true);
-
-        // Save to DB after updated
-        mSubscriptionControllerUT.setDisplayNameUsingSrc(newDisplayName, subId, newNameSource);
-
-        SubscriptionInfo subInfo = mSubscriptionControllerUT
-                .getActiveSubscriptionInfo(subId, mCallingPackage, mCallingFeature);
-
-        assertNotNull(subInfo);
-        assertEquals(DISPLAY_NAME, subInfo.getDisplayName());
-        assertEquals(nameSource, subInfo.getDisplayNameSource());
-    }
-
-    @Test @SmallTest
-    public void testSetGetDisplayNameSrc_updateNameSourceCarrierWithSpnAndCarrierName()
-            throws Exception {
-        testInsertSim();
-
-        // Set values of DB
-        setSimEmbedded(false);
-        int subId = getFirstSubId();
-        int nameSource = SubscriptionManager.NAME_SOURCE_CARRIER;
-        mSubscriptionControllerUT.setDisplayNameUsingSrc(DISPLAY_NAME, subId, nameSource);
-
-        // Update with new value
-        int newNameSource = SubscriptionManager.NAME_SOURCE_SIM_SPN;
-        String carrierName = "testing_carrier_name";
-        String newDisplayName = "display_name_spn";
-        when(mUiccController.getUiccProfileForPhone(anyInt())).thenReturn(mUiccProfile);
-        when(mUiccProfile.getServiceProviderName()).thenReturn(null);
-        mCarrierConfigs.putBoolean(CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL, false);
-        mCarrierConfigs.putString(CarrierConfigManager.KEY_CARRIER_NAME_STRING, carrierName);
-
-        // Save to DB after updated
-        mSubscriptionControllerUT.setDisplayNameUsingSrc(newDisplayName, subId, newNameSource);
-
-        SubscriptionInfo subInfo = mSubscriptionControllerUT
-                .getActiveSubscriptionInfo(subId, mCallingPackage, mCallingFeature);
-
-        assertNotNull(subInfo);
-        assertEquals(DISPLAY_NAME, subInfo.getDisplayName());
-        assertEquals(nameSource, subInfo.getDisplayNameSource());
-    }
-
-    @Test @SmallTest
-    public void testSetGetDisplayNameSrc_updateNameSourcePnnToNameSourceCarrierId()
-            throws Exception {
-        testInsertSim();
-
-        // Set values of DB
-        int subId = getFirstSubId();
-        int nameSource = SubscriptionManager.NAME_SOURCE_SIM_PNN;
-        mSubscriptionControllerUT.setDisplayNameUsingSrc(DISPLAY_NAME, subId, nameSource);
-
-        // Update with new value
-        String newDisplayName = "display_name_carrier_id";
-        int newNameSource = SubscriptionManager.NAME_SOURCE_CARRIER_ID;
-        when(mPhone.getPlmn()).thenReturn("testing_pnn");
-
-        // Save to DB after updated
-        mSubscriptionControllerUT.setDisplayNameUsingSrc(newDisplayName, subId, newNameSource);
-
-        SubscriptionInfo subInfo = mSubscriptionControllerUT
-                .getActiveSubscriptionInfo(subId, mCallingPackage, mCallingFeature);
-
-        assertNotNull(subInfo);
-        assertEquals(DISPLAY_NAME, subInfo.getDisplayName());
-        assertEquals(nameSource, subInfo.getDisplayNameSource());
-    }
-
-    @Test @SmallTest
-    public void testSetGetDisplayNameSrc_updateNameSourceUserInputToNameSourceSpn()
-            throws Exception {
-        testInsertSim();
-
-        // Set values of DB
-        int subId = getFirstSubId();
-        int nameSource = SubscriptionManager.NAME_SOURCE_USER_INPUT;
-        mSubscriptionControllerUT.setDisplayNameUsingSrc(DISPLAY_NAME, subId, nameSource);
-
-        // Update with new value
-        String newDisplayName = "display_name_spn";
-        int newNameSource = SubscriptionManager.NAME_SOURCE_SIM_SPN;
-
-        // Save to DB after updated
-        mSubscriptionControllerUT.setDisplayNameUsingSrc(newDisplayName, subId, newNameSource);
-
-        SubscriptionInfo subInfo = mSubscriptionControllerUT
-                .getActiveSubscriptionInfo(subId, mCallingPackage, mCallingFeature);
-
-        assertNotNull(subInfo);
-        assertEquals(DISPLAY_NAME, subInfo.getDisplayName());
-        assertEquals(nameSource, subInfo.getDisplayNameSource());
-    }
-
-    @Test @SmallTest
-    public void testIsExistingNameSourceStillValid_pnnIsNotNull_returnTrue() {
-        when((mMockSubscriptionInfo).getSubscriptionId()).thenReturn(FAKE_SUBID);
-        when(mMockSubscriptionInfo.getDisplayNameSource())
-                .thenReturn(SubscriptionManager.NAME_SOURCE_SIM_PNN);
-        when(mPhone.getPlmn()).thenReturn("testing_pnn");
-
-        assertTrue(mSubscriptionControllerUT.isExistingNameSourceStillValid(mMockSubscriptionInfo));
-    }
-
-    @Test @SmallTest
-    public void testIsExistingNameSourceStillValid_spnIsNotNull_returnTrue() {
-        when((mMockSubscriptionInfo).getSubscriptionId()).thenReturn(FAKE_SUBID);
-        when(mMockSubscriptionInfo.getDisplayNameSource())
-                .thenReturn(SubscriptionManager.NAME_SOURCE_SIM_SPN);
-        when(mUiccController.getUiccProfileForPhone(anyInt())).thenReturn(mUiccProfile);
-        when(mUiccProfile.getServiceProviderName()).thenReturn("testing_spn");
-
-        assertTrue(mSubscriptionControllerUT.isExistingNameSourceStillValid(mMockSubscriptionInfo));
-    }
-
-    @Test @SmallTest
-    public void testIsExistingNameSourceStillValid_simIsEmbedded_returnTrue() {
-        when(mMockSubscriptionInfo.isEmbedded()).thenReturn(true);
-        when((mMockSubscriptionInfo).getSubscriptionId()).thenReturn(FAKE_SUBID);
-        when(mMockSubscriptionInfo.getDisplayNameSource())
-                .thenReturn(SubscriptionManager.NAME_SOURCE_CARRIER);
-
-        assertTrue(mSubscriptionControllerUT.isExistingNameSourceStillValid(mMockSubscriptionInfo));
-    }
-
-    @Test @SmallTest
-    public void testIsExistingNameSourceStillValid_carrierConfigIsNull_returnTrue() {
-        when(mMockSubscriptionInfo.isEmbedded()).thenReturn(false);
-        when((mMockSubscriptionInfo).getSubscriptionId()).thenReturn(FAKE_SUBID);
-        when(mMockSubscriptionInfo.getDisplayNameSource())
-                .thenReturn(SubscriptionManager.NAME_SOURCE_CARRIER);
-        when(mCarrierConfigManager.getConfigForSubId(FAKE_SUBID)).thenReturn(null);
-
-        assertTrue(mSubscriptionControllerUT.isExistingNameSourceStillValid(mMockSubscriptionInfo));
-    }
-
-    @Test @SmallTest
-    public void testIsExistingNameSourceStillValid_carrierNameOverrideIsTrue_returnTrue() {
-        when(mMockSubscriptionInfo.isEmbedded()).thenReturn(false);
-        when((mMockSubscriptionInfo).getSubscriptionId()).thenReturn(FAKE_SUBID);
-        when(mMockSubscriptionInfo.getDisplayNameSource())
-                .thenReturn(SubscriptionManager.NAME_SOURCE_CARRIER);
-        mCarrierConfigs.putBoolean(CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL, true);
-
-        assertTrue(mSubscriptionControllerUT.isExistingNameSourceStillValid(mMockSubscriptionInfo));
-    }
-
-    @Test @SmallTest
-    public void testIsExistingNameSourceStillValid_spnIsNullAndCarrierNameIsNotNull_returnTrue() {
-        when(mMockSubscriptionInfo.isEmbedded()).thenReturn(false);
-        when((mMockSubscriptionInfo).getSubscriptionId()).thenReturn(FAKE_SUBID);
-        when(mMockSubscriptionInfo.getDisplayNameSource())
-                .thenReturn(SubscriptionManager.NAME_SOURCE_CARRIER);
-        when(mUiccController.getUiccProfileForPhone(anyInt())).thenReturn(mUiccProfile);
-        when(mUiccProfile.getServiceProviderName()).thenReturn(null);
-        mCarrierConfigs.putBoolean(CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL, false);
-        mCarrierConfigs.putString(CarrierConfigManager.KEY_CARRIER_NAME_STRING,
-                "testing_carrier_name");
-
-        assertTrue(mSubscriptionControllerUT.isExistingNameSourceStillValid(mMockSubscriptionInfo));
-    }
-
-    @Test @SmallTest
-    public void testCleanUpSIM() {
-        testInsertSim();
-        assertFalse(mSubscriptionControllerUT.isActiveSubId(2));
-        mSubscriptionControllerUT.clearSubInfo();
-        assertFalse(mSubscriptionControllerUT.isActiveSubId(1));
-        assertEquals(SubscriptionManager.SIM_NOT_INSERTED,
-                mSubscriptionControllerUT.getSlotIndex(1));
-    }
-
-    @Test @SmallTest
-    public void testDefaultSubIdOnSingleSimDevice() {
-        assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
-                mSubscriptionControllerUT.getDefaultDataSubId());
-        assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
-                mSubscriptionControllerUT.getDefaultSmsSubId());
-        assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
-                mSubscriptionControllerUT.getDefaultSmsSubId());
-        /* insert one sim */
-        testInsertSim();
-        // if support single sim, sms/data/voice default sub should be the same
-        assertEquals(1, mSubscriptionControllerUT.getDefaultSubId());
-        assertEquals(1, mSubscriptionControllerUT.getDefaultDataSubId());
-        assertEquals(1, mSubscriptionControllerUT.getDefaultSmsSubId());
-        assertEquals(1, mSubscriptionControllerUT.getDefaultVoiceSubId());
-    }
-
-    @Test @SmallTest
-    public void testSetGetMCCMNC() {
-        testInsertSim();
-        String mCcMncVERIZON = "310004";
-        mSubscriptionControllerUT.setMccMnc(mCcMncVERIZON, 1);
-
-        SubscriptionInfo subInfo = mSubscriptionControllerUT
-                .getActiveSubscriptionInfo(1, mCallingPackage, mCallingFeature);
-        assertNotNull(subInfo);
-        assertEquals(Integer.parseInt(mCcMncVERIZON.substring(0, 3)), subInfo.getMcc());
-        assertEquals(Integer.parseInt(mCcMncVERIZON.substring(3)), subInfo.getMnc());
-    }
-
-    @Test @SmallTest
-    public void testSetGetCarrierId() {
-        testInsertSim();
-        int carrierId = 1234;
-        mSubscriptionControllerUT.setCarrierId(carrierId, 1);
-
-        SubscriptionInfo subInfo = mSubscriptionControllerUT
-                .getActiveSubscriptionInfo(1, mCallingPackage, mCallingFeature);
-        assertNotNull(subInfo);
-        assertEquals(carrierId, subInfo.getCarrierId());
-    }
-
-    @Test
-    @SmallTest
-    public void testSetDefaultDataSubId() throws Exception {
-        doReturn(1).when(mPhone).getSubId();
-
-        mSubscriptionControllerUT.setDefaultDataSubId(1);
-
-        ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
-        verify(mContext, times(1)).sendStickyBroadcastAsUser(
-                captorIntent.capture(), eq(UserHandle.ALL));
-
-        Intent intent = captorIntent.getValue();
-        assertEquals(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED, intent.getAction());
-
-        Bundle b = intent.getExtras();
-
-        assertTrue(b.containsKey(PhoneConstants.SUBSCRIPTION_KEY));
-        assertEquals(1, b.getInt(PhoneConstants.SUBSCRIPTION_KEY));
-    }
-
-    @Test
-    @SmallTest
-    public void testMigrateImsSettings() throws Exception {
-        testInsertSim();
-        int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false);
-        assertTrue(subIds != null && subIds.length != 0);
-        int subID = subIds[0];
-
-        // Set default void subId.
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION,
-                subID);
-
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.ENHANCED_4G_MODE_ENABLED,
-                1);
-
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.VT_IMS_ENABLED,
-                0);
-
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.WFC_IMS_ENABLED,
-                1);
-
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.WFC_IMS_MODE,
-                2);
-
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.WFC_IMS_ROAMING_MODE,
-                3);
-
-        mSubscriptionControllerUT.migrateImsSettings();
-
-        // Global settings should be all set.
-        assertEquals(-1,  Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.ENHANCED_4G_MODE_ENABLED));
-
-        assertEquals(-1,  Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.VT_IMS_ENABLED));
-
-        assertEquals(-1,  Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.WFC_IMS_ENABLED));
-
-        assertEquals(-1,  Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.WFC_IMS_MODE));
-
-        assertEquals(-1,  Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.WFC_IMS_ROAMING_MODE));
-
-        // The values should be migrated to its DB.
-        assertEquals("1", mSubscriptionControllerUT.getSubscriptionProperty(
-                subID,
-                SubscriptionManager.ENHANCED_4G_MODE_ENABLED,
-                mCallingPackage,
-                mCallingFeature));
-
-        assertEquals("0", mSubscriptionControllerUT.getSubscriptionProperty(
-                subID,
-                SubscriptionManager.VT_IMS_ENABLED,
-                mCallingPackage,
-                mCallingFeature));
-
-        assertEquals("1", mSubscriptionControllerUT.getSubscriptionProperty(
-                subID,
-                SubscriptionManager.WFC_IMS_ENABLED,
-                mCallingPackage,
-                mCallingFeature));
-
-        assertEquals("2", mSubscriptionControllerUT.getSubscriptionProperty(
-                subID,
-                SubscriptionManager.WFC_IMS_MODE,
-                mCallingPackage,
-                mCallingFeature));
-
-        assertEquals("3", mSubscriptionControllerUT.getSubscriptionProperty(
-                subID,
-                SubscriptionManager.WFC_IMS_ROAMING_MODE,
-                mCallingPackage,
-                mCallingFeature));
-    }
-
-    @Test
-    @SmallTest
-    public void testSkipMigrateImsSettings() throws Exception {
-
-        // Set default invalid subId.
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION,
-                -1);
-
-        int enhanced4gModeEnabled = 1;
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.ENHANCED_4G_MODE_ENABLED,
-                enhanced4gModeEnabled);
-
-        int vtImsEnabled = 0;
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.VT_IMS_ENABLED,
-                vtImsEnabled);
-
-        int wfcImsEnabled = 1;
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.WFC_IMS_ENABLED,
-                wfcImsEnabled);
-
-        int wfcImsMode = 2;
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.WFC_IMS_MODE,
-                wfcImsMode);
-
-        int wfcImsRoamingMode = 3;
-        Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.WFC_IMS_ROAMING_MODE,
-                wfcImsRoamingMode);
-
-        mSubscriptionControllerUT.migrateImsSettings();
-
-        // Migration should be skipped because subId was invalid
-        assertEquals(enhanced4gModeEnabled, Settings.Global.getInt(mContext.getContentResolver(),
-                    Settings.Global.ENHANCED_4G_MODE_ENABLED));
-
-        assertEquals(vtImsEnabled, Settings.Global.getInt(mContext.getContentResolver(),
-                    Settings.Global.VT_IMS_ENABLED));
-
-        assertEquals(wfcImsEnabled, Settings.Global.getInt(mContext.getContentResolver(),
-                    Settings.Global.WFC_IMS_ENABLED));
-
-        assertEquals(wfcImsMode, Settings.Global.getInt(mContext.getContentResolver(),
-                    Settings.Global.WFC_IMS_MODE));
-
-        assertEquals(wfcImsRoamingMode, Settings.Global.getInt(mContext.getContentResolver(),
-                    Settings.Global.WFC_IMS_ROAMING_MODE));
-    }
-
-    @Test
-    @SmallTest
-    public void testOpptSubInfoListChanged() throws Exception {
-        registerMockTelephonyRegistry();
-        verify(mTelephonyRegistryManager, times(0))
-                .notifyOpportunisticSubscriptionInfoChanged();
-
-        testInsertSim();
-        mSubscriptionControllerUT.addSubInfo("test2", null, 0,
-                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
-
-        // Neither sub1 or sub2 are opportunistic. So getOpportunisticSubscriptions
-        // should return empty list and no callback triggered.
-        List<SubscriptionInfo> opptSubList = mSubscriptionControllerUT
-                .getOpportunisticSubscriptions(mCallingPackage, mCallingFeature);
-
-        assertTrue(opptSubList.isEmpty());
-        verify(mTelephonyRegistryManager, times(0))
-                .notifyOpportunisticSubscriptionInfoChanged();
-
-        // Setting sub2 as opportunistic should trigger callback.
-        mSubscriptionControllerUT.setOpportunistic(true, 2, mCallingPackage);
-
-        verify(mTelephonyRegistryManager, times(1))
-                .notifyOpportunisticSubscriptionInfoChanged();
-        opptSubList = mSubscriptionControllerUT
-                .getOpportunisticSubscriptions(mCallingPackage, mCallingFeature);
-        assertEquals(1, opptSubList.size());
-        assertEquals("test2", opptSubList.get(0).getIccId());
-
-        // Changing non-opportunistic sub1 shouldn't trigger callback.
-        mSubscriptionControllerUT.setDisplayNameUsingSrc("DisplayName", 1,
-                SubscriptionManager.NAME_SOURCE_SIM_SPN);
-        verify(mTelephonyRegistryManager, times(1))
-                .notifyOpportunisticSubscriptionInfoChanged();
-
-        mSubscriptionControllerUT.setDisplayNameUsingSrc("DisplayName", 2,
-                SubscriptionManager.NAME_SOURCE_SIM_SPN);
-        verify(mTelephonyRegistryManager, times(2))
-                .notifyOpportunisticSubscriptionInfoChanged();
-    }
-
-    @Test @SmallTest
-    public void testInsertRemoteSim() {
-        makeThisDeviceMultiSimCapable();
-        mContextFixture.addSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
-
-        // verify there are no sim's in the system.
-        assertEquals(0, mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage,
-                mCallingFeature));
-
-        addAndVerifyRemoteSimAddition(1, 0);
-    }
-
-    private void addAndVerifyRemoteSimAddition(int num, int numOfCurrentSubs) {
-        // Verify the number of current subs in the system
-        assertEquals(numOfCurrentSubs,
-                mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage, mCallingFeature));
-
-        // if there are current subs in the system, get that info
-        List<SubscriptionInfo> mSubList;
-        ArrayList<String> macAddresses = new ArrayList<>();
-        ArrayList<String> displayNames = new ArrayList<>();
-        if (numOfCurrentSubs > 0) {
-            mSubList = mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage,
-                    mCallingFeature);
-            assertNotNull(mSubList);
-            assertEquals(numOfCurrentSubs, mSubList.size());
-            for (SubscriptionInfo info : mSubList) {
-                assertNotNull(info.getIccId());
-                assertNotNull(info.getDisplayName());
-                macAddresses.add(info.getIccId());
-                displayNames.add(info.getDisplayName().toString());
-            }
-        }
-
-        // To add more subs, we need to create macAddresses + displaynames.
-        for (int i = 0; i < num; i++) {
-            macAddresses.add(MAC_ADDRESS_PREFIX + (numOfCurrentSubs + i));
-            displayNames.add(DISPLAY_NAME_PREFIX + (numOfCurrentSubs + i));
-        }
-
-        // Add subs - one at a time and verify the contents in subscription info data structs
-        for (int i = 0; i < num; i++) {
-            int index = numOfCurrentSubs + i;
-            mSubscriptionControllerUT.addSubInfo(macAddresses.get(index), displayNames.get(index),
-                    SubscriptionManager.SLOT_INDEX_FOR_REMOTE_SIM_SUB,
-                    SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM);
-
-            // make sure the subscription is added in SubscriptionController data structs
-            Map<Integer, ArrayList<Integer>> slotIndexToSubsMap =
-                    mSubscriptionControllerUT.getSlotIndexToSubIdsMap();
-            assertNotNull(slotIndexToSubsMap);
-            // Since All remote sim's go to the same slot index, there should only be one entry
-            assertEquals(1, slotIndexToSubsMap.size());
-
-            // get all the subscriptions available. should be what is just added in this method
-            // PLUS the number of subs that already existed before
-            int expectedNumOfSubs = numOfCurrentSubs + i + 1;
-            ArrayList<Integer> subIdsList =
-                    slotIndexToSubsMap.get(SubscriptionManager.SLOT_INDEX_FOR_REMOTE_SIM_SUB);
-            assertNotNull(subIdsList);
-            assertEquals(expectedNumOfSubs, subIdsList.size());
-
-            // validate slot index, sub id etc
-            mSubList = mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage,
-                    mCallingFeature);
-            assertNotNull(mSubList);
-            assertEquals(expectedNumOfSubs, mSubList.size());
-
-            // sort on subscription-id which will make sure the previously existing subscriptions
-            // are in earlier slots in the array
-            mSubList.sort(SUBSCRIPTION_INFO_COMPARATOR);
-
-            // Verify the subscription data. Skip the verification for the existing subs.
-            for (int j = numOfCurrentSubs; j < mSubList.size(); j++) {
-                SubscriptionInfo info = mSubList.get(j);
-                assertTrue(SubscriptionManager.isValidSubscriptionId(info.getSubscriptionId()));
-                assertEquals(SubscriptionManager.SLOT_INDEX_FOR_REMOTE_SIM_SUB,
-                        info.getSimSlotIndex());
-                assertEquals(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM,
-                        info.getSubscriptionType());
-                assertEquals(macAddresses.get(j), info.getIccId());
-                assertEquals(displayNames.get(j), info.getDisplayName());
-            }
-        }
-    }
-
-    private static final Comparator<SubscriptionInfo> SUBSCRIPTION_INFO_COMPARATOR =
-            Comparator.comparingInt(o -> o.getSubscriptionId());
-
-    @Test @SmallTest
-    public void testInsertMultipleRemoteSims() {
-        makeThisDeviceMultiSimCapable();
-        mContextFixture.addSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
-
-        // verify that there are no subscription info records
-        assertEquals(0, mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage,
-                mCallingFeature));
-        Map<Integer, ArrayList<Integer>> slotIndexToSubsMap =
-                mSubscriptionControllerUT.getSlotIndexToSubIdsMap();
-        assertNotNull(slotIndexToSubsMap);
-        assertTrue(slotIndexToSubsMap.isEmpty());
-
-        // Add a few subscriptions
-        addAndVerifyRemoteSimAddition(4, 0);
-    }
-
-    @FlakyTest
-    @Test @SmallTest
-    public void testDefaultSubIdOnMultiSimDevice() {
-        makeThisDeviceMultiSimCapable();
-
-        // Initially, defaults should be -1
-        assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
-                mSubscriptionControllerUT.getDefaultDataSubId());
-        assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
-                mSubscriptionControllerUT.getDefaultSmsSubId());
-        assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
-                mSubscriptionControllerUT.getDefaultSmsSubId());
-
-        // Insert one Remote-Sim.
-        testInsertRemoteSim();
-
-        // defaults should be set to this newly-inserted subscription
-        assertEquals(1, mSubscriptionControllerUT.getDefaultSubId());
-        assertEquals(1, mSubscriptionControllerUT.getDefaultDataSubId());
-        assertEquals(1, mSubscriptionControllerUT.getDefaultSmsSubId());
-        assertEquals(1, mSubscriptionControllerUT.getDefaultVoiceSubId());
-
-        // Add a few subscriptions
-        addAndVerifyRemoteSimAddition(4, 1);
-
-        // defaults should be still be set to the first sub - and unchanged by the addition of
-        // the above multiple sims.
-        assertEquals(1, mSubscriptionControllerUT.getDefaultSubId());
-        assertEquals(1, mSubscriptionControllerUT.getDefaultDataSubId());
-        assertEquals(1, mSubscriptionControllerUT.getDefaultSmsSubId());
-        assertEquals(1, mSubscriptionControllerUT.getDefaultVoiceSubId());
-    }
-
-    @Test @SmallTest
-    public void testRemoveSubscription() {
-        makeThisDeviceMultiSimCapable();
-
-        /* insert some sims */
-        testInsertMultipleRemoteSims();
-        assertEquals(1, mSubscriptionControllerUT.getDefaultSubId());
-        int[] subIdsArray = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false);
-        assertTrue(subIdsArray.length > 0);
-        int len = subIdsArray.length;
-
-        // remove the first sim - which also is the default sim.
-        int result = mSubscriptionControllerUT.removeSubInfo(MAC_ADDRESS_PREFIX + 0,
-                SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM);
-
-        assertTrue(result > 0);
-        // now check the number of subs left. should be one less than earlier
-        int[] newSubIdsArray = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false);
-        assertTrue(newSubIdsArray.length > 0);
-        assertEquals(len - 1, newSubIdsArray.length);
-
-        // now check that there is a new default
-        assertNotSame(1, mSubscriptionControllerUT.getDefaultSubId());
-    }
-
-    private void makeThisDeviceMultiSimCapable() {
-        doReturn(10).when(mTelephonyManager).getSimCount();
-    }
-
-    @Test
-    @SmallTest
-    public void testSetSubscriptionGroupWithModifyPermission() throws Exception {
-        testInsertSim();
-        mSubscriptionControllerUT.addSubInfo("test2", null, 0,
-                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
-
-        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
-        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
-
-        int[] subIdList = new int[] {1, 2};
-        try {
-            mSubscriptionControllerUT.createSubscriptionGroup(
-                    subIdList, mContext.getOpPackageName());
-            fail("createSubscriptionGroup should fail with no permission.");
-        } catch (SecurityException e) {
-            // Expected result.
-        }
-
-        // With modify permission it should succeed.
-        mContextFixture.addCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE);
-        ParcelUuid groupId = mSubscriptionControllerUT.createSubscriptionGroup(
-                subIdList, mContext.getOpPackageName());
-        assertNotEquals(null, groupId);
-
-        // Calling it again should generate a new group ID.
-        ParcelUuid newGroupId = mSubscriptionControllerUT.createSubscriptionGroup(
-                subIdList, mContext.getOpPackageName());
-        assertNotEquals(null, newGroupId);
-        assertNotEquals(groupId, newGroupId);
-    }
-
-    @Test
-    @SmallTest
-    public void testGetSubscriptionProperty() throws Exception {
-        testInsertSim();
-        ContentValues values = new ContentValues();
-        values.put(SubscriptionManager.GROUP_UUID, 1);
-        mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values,
-                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + 1, null);
-
-        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
-        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
-
-        // should succeed with read phone state permission
-        String prop = mSubscriptionControllerUT.getSubscriptionProperty(1,
-                SubscriptionManager.CB_EXTREME_THREAT_ALERT, mContext.getOpPackageName(),
-                mContext.getAttributionTag());
-
-        assertNotEquals(null, prop);
-
-        // group UUID requires privileged phone state permission
-        prop = mSubscriptionControllerUT.getSubscriptionProperty(1, SubscriptionManager.GROUP_UUID,
-                    mContext.getOpPackageName(), mContext.getAttributionTag());
-        assertEquals(null, prop);
-
-        // group UUID should succeed once privileged phone state permission is granted
-        setupReadPrivilegePermission();
-        prop = mSubscriptionControllerUT.getSubscriptionProperty(1, SubscriptionManager.GROUP_UUID,
-                mContext.getOpPackageName(), mContext.getAttributionTag());
-        assertNotEquals(null, prop);
-    }
-
-    @Test
-    @SmallTest
-    public void testCreateSubscriptionGroupWithCarrierPrivilegePermission() throws Exception {
-        testInsertSim();
-        // Adding a second profile and mark as embedded.
-        // TODO b/123300875 slot index 1 is not expected to be valid
-        mSubscriptionControllerUT.addSubInfo("test2", null, 1,
-                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
-
-        ContentValues values = new ContentValues();
-        values.put(SubscriptionManager.IS_EMBEDDED, 1);
-        mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values,
-                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + 2, null);
-        mSubscriptionControllerUT.refreshCachedActiveSubscriptionInfoList();
-
-        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
-        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
-
-        int[] subIdList = new int[] {1, 2};
-        // It should fail since it has no permission.
-        try {
-            mSubscriptionControllerUT.createSubscriptionGroup(
-                    subIdList, mContext.getOpPackageName());
-            fail("createSubscriptionGroup should fail with no permission.");
-        } catch (SecurityException e) {
-            // Expected result.
-        }
-
-        doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(1);
-        try {
-            mSubscriptionControllerUT.createSubscriptionGroup(
-                    subIdList, mContext.getOpPackageName());
-            fail("createSubscriptionGroup should fail with no permission on sub 2.");
-        } catch (SecurityException e) {
-            // Expected result.
-        }
-
-        doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(2);
-        ParcelUuid groupId = mSubscriptionControllerUT.createSubscriptionGroup(
-                subIdList, mContext.getOpPackageName());
-        assertNotEquals(null, groupId);
-
-        List<SubscriptionInfo> subInfoList =
-                mSubscriptionControllerUT.getActiveSubscriptionInfoList(mContext.getOpPackageName(),
-                        mContext.getAttributionTag());
-
-        // Put sub3 into slot 1 to make sub2 inactive.
-        mContextFixture.addCallingOrSelfPermission(
-                android.Manifest.permission.MODIFY_PHONE_STATE);
-        // TODO b/123300875 slot index 1 is not expected to be valid
-        mSubscriptionControllerUT.addSubInfo("test3", null, 1,
-                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
-
-        mContextFixture.removeCallingOrSelfPermission(
-                android.Manifest.permission.MODIFY_PHONE_STATE);
-        // As sub2 is inactive, it will checks carrier privilege against access rules in the db.
-        doReturn(true).when(mSubscriptionManager).canManageSubscription(
-                eq(subInfoList.get(1)), anyString());
-
-        ParcelUuid newGroupId = mSubscriptionControllerUT.createSubscriptionGroup(
-                subIdList, mContext.getOpPackageName());
-        assertNotEquals(null, newGroupId);
-        assertNotEquals(groupId, newGroupId);
-    }
-
-    @Test
-    @SmallTest
-    @EnableCompatChanges({REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID})
-    public void testAddSubscriptionIntoGroupWithCarrierPrivilegePermission() throws Exception {
-        testInsertSim();
-        // Adding a second profile and mark as embedded.
-        // TODO b/123300875 slot index 1 is not expected to be valid
-        mSubscriptionControllerUT.addSubInfo("test2", null, 1,
-                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
-
-        ContentValues values = new ContentValues();
-        values.put(SubscriptionManager.IS_EMBEDDED, 1);
-        mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values,
-                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + 2, null);
-        mSubscriptionControllerUT.refreshCachedActiveSubscriptionInfoList();
-
-        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
-        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
-
-        // Create group for sub 1.
-        int[] subIdList = new int[] {1};
-        doReturn(subIdList).when(mSubscriptionManager).getCompleteActiveSubscriptionIdList();
-        doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(1);
-        ParcelUuid groupId = mSubscriptionControllerUT.createSubscriptionGroup(
-                subIdList, "packageName1");
-
-        // Try to add sub 2 into group of sub 1.
-        // Should fail as it doesn't have carrier privilege on sub 2.
-        try {
-            mSubscriptionControllerUT.addSubscriptionsIntoGroup(
-                    new int[] {2}, groupId, "packageName1");
-            fail("addSubscriptionsIntoGroup should fail with no permission on sub 2.");
-        } catch (SecurityException e) {
-            // Expected result.
-        }
-
-        doReturn(false).when(mTelephonyManager).hasCarrierPrivileges(1);
-        doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(2);
-        // Try to add sub 2 into group of sub 1.
-        // Should fail as it doesn't have carrier privilege on sub 1.
-        try {
-            mSubscriptionControllerUT.addSubscriptionsIntoGroup(
-                    new int[] {2}, groupId, "packageName2");
-            fail("addSubscriptionsIntoGroup should fail with no permission on the group (sub 1).");
-        } catch (SecurityException e) {
-            // Expected result.
-        }
-
-        doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(1);
-        mSubscriptionControllerUT.addSubscriptionsIntoGroup(new int[] {2}, groupId, "packageName2");
-        List<SubscriptionInfo> infoList = mSubscriptionControllerUT
-                .getSubscriptionsInGroup(groupId, "packageName2", "feature2");
-        assertEquals(2, infoList.size());
-    }
-
-    @Test
-    @SmallTest
-    @EnableCompatChanges({REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID})
-    public void testUpdateSubscriptionGroupWithCarrierPrivilegePermission() throws Exception {
-        testInsertSim();
-        // Adding a second profile and mark as embedded.
-        // TODO b/123300875 slot index 1 is not expected to be valid
-        mSubscriptionControllerUT.addSubInfo("test2", null, 1,
-                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
-
-        ContentValues values = new ContentValues();
-        values.put(SubscriptionManager.IS_EMBEDDED, 1);
-        mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values,
-                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + 2, null);
-        mSubscriptionControllerUT.refreshCachedActiveSubscriptionInfoList();
-
-        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
-        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
-
-        int[] subIdList = new int[] {1};
-
-        doReturn(subIdList).when(mSubscriptionManager).getCompleteActiveSubscriptionIdList();
-        doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(1);
-        doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(2);
-
-        ParcelUuid groupId = mSubscriptionControllerUT.createSubscriptionGroup(
-                subIdList, "packageName1");
-        assertNotEquals(null, groupId);
-
-        mSubscriptionControllerUT.addSubscriptionsIntoGroup(
-                new int[] {2}, groupId, "packageName1");
-        List<SubscriptionInfo> infoList = mSubscriptionControllerUT.getSubscriptionsInGroup(
-                groupId, "packageName1", "feature1");
-        assertEquals(2, infoList.size());
-        assertEquals(1, infoList.get(0).getSubscriptionId());
-        assertEquals(2, infoList.get(1).getSubscriptionId());
-
-        mSubscriptionControllerUT.removeSubscriptionsFromGroup(
-                new int[] {2}, groupId, "packageName1");
-        infoList = mSubscriptionControllerUT.getSubscriptionsInGroup(
-                groupId, "packageName1", "feature1");
-        assertEquals(1, infoList.size());
-        assertEquals(1, infoList.get(0).getSubscriptionId());
-
-        // Make sub 1 inactive.
-        mSubscriptionControllerUT.clearSubInfoRecord(0);
-
-        try {
-            mSubscriptionControllerUT.addSubscriptionsIntoGroup(
-                    new int[] {2}, groupId, "packageName2");
-            fail("addSubscriptionsIntoGroup should fail with wrong callingPackage name");
-        } catch (SecurityException e) {
-            // Expected result.
-        }
-
-        // Adding and removing subscription should still work for packageName1, as it's the group
-        // owner who created the group earlier..
-        mSubscriptionControllerUT.addSubscriptionsIntoGroup(
-                new int[] {2}, groupId, "packageName1");
-        infoList = mSubscriptionControllerUT.getSubscriptionsInGroup(
-                groupId, "packageName1", "feature1");
-        assertEquals(2, infoList.size());
-        assertEquals(1, infoList.get(0).getSubscriptionId());
-        assertEquals(2, infoList.get(1).getSubscriptionId());
-
-        mSubscriptionControllerUT.removeSubscriptionsFromGroup(
-                new int[] {2}, groupId, "packageName1");
-        infoList = mSubscriptionControllerUT.getSubscriptionsInGroup(
-                groupId, "packageName1", "feature1");
-        assertEquals(1, infoList.size());
-        assertEquals(1, infoList.get(0).getSubscriptionId());
-    }
-
-    @Test
-    @SmallTest
-    public void testDisabledSubscriptionGroup() throws Exception {
-        registerMockTelephonyRegistry();
-
-        testInsertSim();
-        // Adding a second profile and mark as embedded.
-        mSubscriptionControllerUT.addSubInfo("test2", null, 0,
-                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
-
-        ContentValues values = new ContentValues();
-        values.put(SubscriptionManager.IS_EMBEDDED, 1);
-        values.put(SubscriptionManager.IS_OPPORTUNISTIC, 1);
-        mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values,
-                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + 2, null);
-        mSubscriptionControllerUT.refreshCachedActiveSubscriptionInfoList();
-        mSubscriptionControllerUT.notifySubscriptionInfoChanged();
-
-        verify(mTelephonyRegistryManager, times(1))
-                .notifyOpportunisticSubscriptionInfoChanged();
-
-        // Set sub 1 and 2 into same group.
-        int[] subIdList = new int[] {1, 2};
-        ParcelUuid groupId = mSubscriptionControllerUT.createSubscriptionGroup(
-                subIdList, mContext.getOpPackageName());
-        assertNotEquals(null, groupId);
-
-        mSubscriptionControllerUT.notifySubscriptionInfoChanged();
-        verify(mTelephonyRegistryManager, times(2))
-                .notifyOpportunisticSubscriptionInfoChanged();
-        List<SubscriptionInfo> opptSubList = mSubscriptionControllerUT
-                .getOpportunisticSubscriptions(mCallingPackage, mCallingFeature);
-        assertEquals(1, opptSubList.size());
-        assertEquals(2, opptSubList.get(0).getSubscriptionId());
-        assertEquals(false, opptSubList.get(0).isGroupDisabled());
-
-        // Unplug SIM 1. This should trigger subscription controller disabling sub 2.
-        values = new ContentValues();
-        values.put(SubscriptionManager.SIM_SLOT_INDEX, -1);
-        mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values,
-                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + 1, null);
-        mSubscriptionControllerUT.refreshCachedActiveSubscriptionInfoList();
-        mSubscriptionControllerUT.notifySubscriptionInfoChanged();
-
-        verify(mTelephonyRegistryManager, times(3))
-                .notifyOpportunisticSubscriptionInfoChanged();
-        opptSubList = mSubscriptionControllerUT.getOpportunisticSubscriptions(mCallingPackage,
-                mCallingFeature);
-        assertEquals(1, opptSubList.size());
-        assertEquals(2, opptSubList.get(0).getSubscriptionId());
-        assertEquals(true, opptSubList.get(0).isGroupDisabled());
-    }
-
-    @Test
-    @SmallTest
-    @EnableCompatChanges({REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID})
-    public void testSetSubscriptionGroup() throws Exception {
-        testInsertSim();
-        // Adding a second profile and mark as embedded.
-        mSubscriptionControllerUT.addSubInfo("test2", null, 1,
-                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
-
-        ContentValues values = new ContentValues();
-        values.put(SubscriptionManager.IS_EMBEDDED, 1);
-        mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values,
-                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + 2, null);
-        mSubscriptionControllerUT.refreshCachedActiveSubscriptionInfoList();
-
-        assertTrue(mSubscriptionControllerUT.isActiveSubId(1));
-        assertTrue(mSubscriptionControllerUT.isActiveSubId(2));
-        assertTrue(TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, 1,
-                mContext.getOpPackageName(), mContext.getAttributionTag(),
-                "getSubscriptionsInGroup"));
-
-        int[] subIdList = new int[] {1};
-        ParcelUuid groupUuid = mSubscriptionControllerUT.createSubscriptionGroup(
-                subIdList, mContext.getOpPackageName());
-        assertNotEquals(null, groupUuid);
-
-        // Sub 1 and sub 2 should be in same group.
-        doReturn(subIdList).when(mSubscriptionManager).getCompleteActiveSubscriptionIdList();
-        List<SubscriptionInfo> infoList = mSubscriptionControllerUT.getSubscriptionsInGroup(
-                groupUuid, mContext.getOpPackageName(), mContext.getAttributionTag());
-        assertNotEquals(null, infoList);
-        assertEquals(1, infoList.size());
-        assertEquals(1, infoList.get(0).getSubscriptionId());
-
-        subIdList = new int[] {2};
-
-        mSubscriptionControllerUT.addSubscriptionsIntoGroup(
-                subIdList, groupUuid, mContext.getOpPackageName());
-        infoList = mSubscriptionControllerUT.getSubscriptionsInGroup(groupUuid,
-                mContext.getOpPackageName(), mContext.getAttributionTag());
-        assertEquals(2, infoList.size());
-        assertEquals(2, infoList.get(1).getSubscriptionId());
-
-        // Remove group of sub 1.
-        subIdList = new int[] {1};
-        mSubscriptionControllerUT.removeSubscriptionsFromGroup(
-                subIdList, groupUuid, mContext.getOpPackageName());
-        infoList = mSubscriptionControllerUT.getSubscriptionsInGroup(groupUuid,
-                mContext.getOpPackageName(), mContext.getAttributionTag());
-        assertEquals(1, infoList.size());
-        assertEquals(2, infoList.get(0).getSubscriptionId());
-
-        // Adding sub 1 into a non-existing UUID, which should be granted.
-        groupUuid = new ParcelUuid(UUID.randomUUID());
-        mSubscriptionControllerUT.addSubscriptionsIntoGroup(
-                subIdList, groupUuid, mContext.getOpPackageName());
-        infoList = mSubscriptionControllerUT.getSubscriptionsInGroup(groupUuid,
-                mContext.getOpPackageName(), mContext.getAttributionTag());
-        assertEquals(1, infoList.size());
-        assertEquals(1, infoList.get(0).getSubscriptionId());
-    }
-
-    private void registerMockTelephonyRegistry() {
-        mServiceManagerMockedServices.put("telephony.registry", mTelephonyRegistryMock);
-        doReturn(mTelephonyRegistryMock).when(mTelephonyRegistryMock)
-                .queryLocalInterface(anyString());
-    }
-
-    @Test
-    @SmallTest
-    public void testEnableDisableSubscriptionSanity() throws Exception {
-        testInsertSim();
-
-        // Non existing subId.
-        assertFalse(mSubscriptionControllerUT.isSubscriptionEnabled(2));
-
-        // Test invalid arguments.
-        try {
-            assertFalse(mSubscriptionControllerUT.isSubscriptionEnabled(-1));
-            fail("Should throw IllegalArgumentException with invalid subId.");
-        } catch (IllegalArgumentException exception) {
-            // Expected.
-        }
-
-        try {
-            mSubscriptionControllerUT.getEnabledSubscriptionId(3);
-            fail("Should throw IllegalArgumentException with invalid subId.");
-        } catch (IllegalArgumentException exception) {
-            // Expected.
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void testGetActiveSubIdList() throws Exception {
-        // TODO b/123300875 slot index 1 is not expected to be valid
-        mSubscriptionControllerUT.addSubInfo("123", null, 1,
-                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); // sub 1
-        mSubscriptionControllerUT.addSubInfo("456", null, 0,
-                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); // sub 2
-
-        int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false);
-        // Make sure the return sub ids are sorted by slot index
-        assertTrue("active sub ids = " + Arrays.toString(subIds),
-                Arrays.equals(subIds, new int[]{2, 1}));
-    }
-
-    @Test
-    public void testGetActiveSubscriptionInfoWithNoPermissions() throws Exception {
-        // If the calling package does not have the READ_PHONE_STATE permission or carrier
-        // privileges then getActiveSubscriptionInfo should throw a SecurityException;
-        testInsertSim();
-        setupReadPrivilegePermission();
-        int subId = getFirstSubId();
-        removeReadPrivilegePermission();
-        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
-
-        try {
-            mSubscriptionControllerUT.getActiveSubscriptionInfo(subId, mCallingPackage,
-                    mCallingFeature);
-            fail("getActiveSubscriptionInfo should fail when invoked with no permissions");
-        } catch (SecurityException expected) {
-        }
-    }
-
-    @Test
-    public void testGetActiveSubscriptionInfoWithReadPhoneState() throws Exception {
-        // If the calling package only has the READ_PHONE_STATE permission then
-        // getActiveSubscriptionInfo should still return a result but the ICC ID should not be
-        // available via getIccId or getCardString.
-        testInsertSim();
-        setupReadPhoneNumbersTest();
-        setIdentifierAccess(false);
-        setupReadPrivilegePermission();
-        int subId = getFirstSubId();
-
-        SubscriptionInfo subscriptionInfo = mSubscriptionControllerUT.getActiveSubscriptionInfo(
-                subId, mCallingPackage, mCallingFeature);
-
-        assertNotNull(subscriptionInfo);
-        assertEquals(UNAVAILABLE_ICCID, subscriptionInfo.getIccId());
-        assertEquals(UNAVAILABLE_ICCID, subscriptionInfo.getCardString());
-        assertEquals(UNAVAILABLE_NUMBER, subscriptionInfo.getNumber());
-    }
-
-    @Test
-    public void testGetActiveSubscriptionWithPhoneNumberAccess() throws Exception {
-        // If the calling package meets any of the requirements for the
-        // LegacyPermissionManager#checkPhoneNumberAccess test then the number should be available
-        // in the SubscriptionInfo.
-        testInsertSim();
-        setupReadPhoneNumbersTest();
-        setPhoneNumberAccess(PackageManager.PERMISSION_GRANTED);
-        setupReadPrivilegePermission();
-        int subId = getFirstSubId();
-
-        SubscriptionInfo subscriptionInfo = mSubscriptionControllerUT.getActiveSubscriptionInfo(
-                subId, mCallingPackage, mCallingFeature);
-
-        assertNotNull(subscriptionInfo);
-        assertEquals(DISPLAY_NUMBER, subscriptionInfo.getNumber());
-    }
-
-    @Test
-    public void testGetActiveSubscriptionInfoWithCarrierPrivileges() throws Exception {
-        // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier
-        // privileges the ICC ID should be available in the SubscriptionInfo.
-        testInsertSim();
-        setupIdentifierCarrierPrivilegesTest();
-        setupReadPrivilegePermission();
-        int subId = getFirstSubId();
-
-        SubscriptionInfo subscriptionInfo = mSubscriptionControllerUT.getActiveSubscriptionInfo(
-                subId, mCallingPackage, mCallingFeature);
-
-        assertNotNull(subscriptionInfo);
-        assertTrue(subscriptionInfo.getIccId().length() > 0);
-        assertTrue(subscriptionInfo.getCardString().length() > 0);
-    }
-
-    @Test
-    public void testGetActiveSubscriptionWithPrivilegedPermission() throws Exception {
-        // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier
-        // privileges the ICC ID should be available in the SubscriptionInfo.
-        testInsertSim();
-        int subId = getFirstSubId();
-
-        SubscriptionInfo subscriptionInfo = mSubscriptionControllerUT.getActiveSubscriptionInfo(
-                subId, mCallingPackage, mCallingFeature);
-
-        assertNotNull(subscriptionInfo);
-        assertTrue(subscriptionInfo.getIccId().length() > 0);
-        assertTrue(subscriptionInfo.getCardString().length() > 0);
-    }
-
-    @Test
-    public void testGetActiveSubscriptionInfoForSimSlotIndexWithNoPermission() throws Exception {
-        // If the calling package does not have the READ_PHONE_STATE permission or carrier
-        // privileges then getActiveSubscriptionInfoForSimSlotIndex should throw a
-        // SecurityException.
-        testInsertSim();
-        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
-
-        try {
-            mSubscriptionControllerUT.getActiveSubscriptionInfoForSimSlotIndex(0, mCallingPackage,
-                    mCallingFeature);
-            fail("getActiveSubscriptionInfoForSimSlotIndex should fail when invoked with no "
-                    + "permissions");
-        } catch (SecurityException expected) {
-        }
-    }
-
-    @Test
-    public void testGetActiveSubscriptionInfoForSimSlotIndexWithReadPhoneState() throws Exception {
-        // If the calling package only has the READ_PHONE_STATE permission then
-        // getActiveSubscriptionInfoForSimlSlotIndex should still return the SubscriptionInfo but
-        // the ICC ID should not be available via getIccId or getCardString.
-        testInsertSim();
-        setupReadPhoneNumbersTest();
-        setIdentifierAccess(false);
-
-        SubscriptionInfo subscriptionInfo =
-                mSubscriptionControllerUT.getActiveSubscriptionInfoForSimSlotIndex(0,
-                        mCallingPackage, mCallingFeature);
-
-        assertNotNull(subscriptionInfo);
-        assertEquals(UNAVAILABLE_ICCID, subscriptionInfo.getIccId());
-        assertEquals(UNAVAILABLE_ICCID, subscriptionInfo.getCardString());
-        assertEquals(UNAVAILABLE_NUMBER, subscriptionInfo.getNumber());
-    }
-
-    @Test
-    public void testGetActiveSubscriptionInfoForSimSlotIndexWithPhoneNumberAccess()
-            throws Exception {
-        // If the calling package meets any of the requirements for the
-        // LegacyPermissionManager#checkPhoneNumberAccess test then the number should be available
-        // in the SubscriptionInfo.
-        testInsertSim();
-        setupReadPhoneNumbersTest();
-        setPhoneNumberAccess(PackageManager.PERMISSION_GRANTED);
-
-        SubscriptionInfo subscriptionInfo =
-                mSubscriptionControllerUT.getActiveSubscriptionInfoForSimSlotIndex(0,
-                        mCallingPackage, mCallingFeature);
-
-        assertNotNull(subscriptionInfo);
-        assertEquals(DISPLAY_NUMBER, subscriptionInfo.getNumber());
-    }
-
-    @Test
-    public void testGetActiveSubscriptionInfoForSimSlotIndexWithCarrierPrivileges()
-            throws Exception {
-        // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier
-        // privileges the ICC ID should be available in the SubscriptionInfo.
-        testInsertSim();
-        setupIdentifierCarrierPrivilegesTest();
-
-        SubscriptionInfo subscriptionInfo =
-                mSubscriptionControllerUT.getActiveSubscriptionInfoForSimSlotIndex(0,
-                        mCallingPackage, mCallingFeature);
-
-        assertNotNull(subscriptionInfo);
-        assertTrue(subscriptionInfo.getIccId().length() > 0);
-        assertTrue(subscriptionInfo.getCardString().length() > 0);
-    }
-
-    @Test
-    public void testGetActiveSubscriptionInfoForSimSlotIndexWithPrivilegedPermission()
-            throws Exception {
-        // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier
-        // privileges the ICC ID should be available in the SubscriptionInfo.
-        testInsertSim();
-
-        SubscriptionInfo subscriptionInfo =
-                mSubscriptionControllerUT.getActiveSubscriptionInfoForSimSlotIndex(0,
-                        mCallingPackage, mCallingFeature);
-
-        assertNotNull(subscriptionInfo);
-        assertTrue(subscriptionInfo.getIccId().length() > 0);
-        assertTrue(subscriptionInfo.getCardString().length() > 0);
-    }
-
-    @Test
-    public void testGetActiveSubscriptionInfoListWithNoPermission() throws Exception {
-        // If the calling package does not have the READ_PHONE_STATE permission or carrier
-        // privileges then getActiveSubscriptionInfoList should return a list with 0 elements.
-        testInsertSim();
-        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
-
-        List<SubscriptionInfo> subInfoList =
-                mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage,
-                        mCallingFeature);
-
-        assertNotNull(subInfoList);
-        assertTrue(subInfoList.size() == 0);
-    }
-
-    @Test
-    public void testGetActiveSubscriptionInfoListWithReadPhoneState() throws Exception {
-        // If the calling package only has the READ_PHONE_STATE permission then
-        // getActiveSubscriptionInfoList should still return the list of SubscriptionInfo objects
-        // but the ICC ID should not be available via getIccId or getCardString.
-        testInsertSim();
-        setupReadPhoneNumbersTest();
-        setIdentifierAccess(false);
-
-        List<SubscriptionInfo> subInfoList =
-                mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage,
-                        mCallingFeature);
-
-        assertTrue(subInfoList.size() > 0);
-        for (SubscriptionInfo info : subInfoList) {
-            assertEquals(UNAVAILABLE_ICCID, info.getIccId());
-            assertEquals(UNAVAILABLE_ICCID, info.getCardString());
-            assertEquals(UNAVAILABLE_NUMBER, info.getNumber());
-        }
-    }
-
-    @Test
-    public void testGetActiveSubscriptionInfoListWithPhoneNumberAccess() throws Exception {
-        // If the calling package meets any of the requirements for the
-        // LegacyPermissionManager#checkPhoneNumberAccess test then the number should be available
-        // in the SubscriptionInfo.
-        testInsertSim();
-        setupReadPhoneNumbersTest();
-        setPhoneNumberAccess(PackageManager.PERMISSION_GRANTED);
-
-        List<SubscriptionInfo> subInfoList =
-                mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage,
-                        mCallingFeature);
-
-        assertTrue(subInfoList.size() > 0);
-        SubscriptionInfo subInfo = subInfoList.get(0);
-        assertEquals(DISPLAY_NUMBER, subInfo.getNumber());
-    }
-
-    @Test
-    public void testGetActiveSubscriptionInfoListWithCarrierPrivileges() throws Exception {
-        // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier
-        // privileges the ICC ID should be available in the SubscriptionInfo objects in the List.
-        testInsertSim();
-        setupIdentifierCarrierPrivilegesTest();
-
-        List<SubscriptionInfo> subInfoList =
-                mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage,
-                        mCallingFeature);
-
-        assertTrue(subInfoList.size() > 0);
-        for (SubscriptionInfo info : subInfoList) {
-            assertTrue(info.getIccId().length() > 0);
-            assertTrue(info.getCardString().length() > 0);
-        }
-    }
-
-    @Test
-    public void testGetActiveSubscriptionInfoListWithCarrierPrivilegesOnOneSubId()
-            throws Exception {
-        // If an app does not have the READ_PHONE_STATE permission but has carrier privileges on one
-        // out of multiple sub IDs then the SubscriptionInfo for that subId should be returned with
-        // the ICC ID and phone number.
-        testInsertSim();
-        doReturn(2).when(mTelephonyManager).getPhoneCount();
-        mSubscriptionControllerUT.addSubInfo("test2", null, 1,
-                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
-
-        int firstSubId = getFirstSubId();
-        int secondSubId = getSubIdAtIndex(1);
-        mSubscriptionControllerUT.setDisplayNumber(DISPLAY_NUMBER, secondSubId);
-        setupIdentifierCarrierPrivilegesTest();
-        mContextFixture.removeCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
-        setCarrierPrivilegesForSubId(false, firstSubId);
-        setCarrierPrivilegesForSubId(true, secondSubId);
-
-        List<SubscriptionInfo> subInfoList =
-                mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage,
-                        mCallingFeature);
-
-        assertEquals(1, subInfoList.size());
-        SubscriptionInfo subInfo = subInfoList.get(0);
-        assertEquals("test2", subInfo.getIccId());
-        assertEquals(DISPLAY_NUMBER, subInfo.getNumber());
-    }
-
-    @Test
-    public void testGetActiveSubscriptionInfoListCheckOrder()
-            throws Exception {
-        // If an app does not have the READ_PHONE_STATE permission but has carrier privileges on one
-        // out of multiple sub IDs then the SubscriptionInfo for that subId should be returned with
-        // the ICC ID and phone number.
-        testInsertSim();
-        doReturn(2).when(mTelephonyManager).getPhoneCount();
-        mSubscriptionControllerUT.addSubInfo("test2", null, 1,
-                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
-        int firstSubId = getFirstSubId();
-        int secondSubId = getSubIdAtIndex(1);
-        setupIdentifierCarrierPrivilegesTest();
-        setCarrierPrivilegesForSubId(false, firstSubId);
-        setCarrierPrivilegesForSubId(true, secondSubId);
-
-        List<SubscriptionInfo> subInfoList =
-                mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage,
-                        mCallingFeature);
-
-        assertEquals(2, subInfoList.size());
-        assertTrue(subInfoList.get(0).getSubscriptionId() < subInfoList.get(1).getSubscriptionId());
-    }
-
-    @Test
-    public void testGetActiveSubscriptionInfoListWithIdentifierAccessWithoutNumberAccess()
-            throws Exception {
-        // An app with access to device identifiers may not have access to the device phone number
-        // (ie an app that passes the device / profile owner check or an app that has been granted
-        // the device identifiers appop); this test verifies that an app with identifier access
-        // can read the ICC ID but does not receive the phone number.
-        testInsertSim();
-        setupReadPhoneNumbersTest();
-        setIdentifierAccess(true);
-
-        List<SubscriptionInfo> subInfoList =
-                mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage,
-                        mCallingFeature);
-
-        assertEquals(1, subInfoList.size());
-        SubscriptionInfo subInfo = subInfoList.get(0);
-        assertEquals("test", subInfo.getIccId());
-        assertEquals(UNAVAILABLE_NUMBER, subInfo.getNumber());
-    }
-
-    @Test
-    public void testGetActiveSubscriptionInfoListWithPrivilegedPermission() throws Exception {
-        // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier
-        // privileges the ICC ID should be available in the SubscriptionInfo objects in the List.
-        testInsertSim();
-
-        List<SubscriptionInfo> subInfoList =
-                mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage,
-                        mCallingFeature);
-
-        assertTrue(subInfoList.size() > 0);
-        for (SubscriptionInfo info : subInfoList) {
-            assertTrue(info.getIccId().length() > 0);
-            assertTrue(info.getCardString().length() > 0);
-        }
-    }
-
-    @Test
-    @DisableCompatChanges({REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID})
-    public void testGetSubscriptionsInGroupWithReadPhoneState() throws Exception {
-        // For backward compatibility test
-        ParcelUuid groupUuid = setupGetSubscriptionsInGroupTest();
-        setupReadPhoneNumbersTest();
-        setIdentifierAccess(false);
-
-        List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getSubscriptionsInGroup(
-                groupUuid, mCallingPackage, mCallingFeature);
-
-        assertTrue(subInfoList.size() > 0);
-        for (SubscriptionInfo info : subInfoList) {
-            assertEquals(UNAVAILABLE_ICCID, info.getIccId());
-            assertEquals(UNAVAILABLE_ICCID, info.getCardString());
-            assertEquals(UNAVAILABLE_NUMBER, info.getNumber());
-        }
-    }
-
-    @Test
-    @EnableCompatChanges({REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID})
-    public void testGetSubscriptionsInGroupWithoutAppropriatePermission() throws Exception {
-        ParcelUuid groupUuid = setupGetSubscriptionsInGroupTest();
-
-        // no permission
-        setNoPermission();
-        try {
-            mSubscriptionControllerUT.getSubscriptionsInGroup(groupUuid, mCallingPackage,
-                    mCallingFeature);
-            fail("getSubscriptionsInGroup should fail when invoked with no permissions");
-        } catch (SecurityException expected) {
-        }
-
-        // only has the device identifiers permission
-        setIdentifierAccess(true);
-        try {
-            mSubscriptionControllerUT.getSubscriptionsInGroup(groupUuid, mCallingPackage,
-                    mCallingFeature);
-            fail("getSubscriptionsInGroup should fail when invoked with no"
-                    + "READ_PHONE_STATE permissions");
-        } catch (SecurityException expected) {
-        }
-
-        // only has the READ_PHONE_STATE permission
-        setIdentifierAccess(false);
-        setReadPhoneState();
-        List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getSubscriptionsInGroup(
-                groupUuid, mCallingPackage, mCallingFeature);
-        assertNotNull(subInfoList);
-        assertTrue(subInfoList.isEmpty());
-    }
-
-    @Test
-    @EnableCompatChanges({REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID})
-    public void testGetSubscriptionsInGroupWithReadDeviceIdentifier() throws Exception {
-        ParcelUuid groupUuid = setupGetSubscriptionsInGroupTest();
-        setNoPermission();
-        setCarrierPrivileges(false);
-        setIdentifierAccess(true);
-        setReadPhoneState();
-
-        List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getSubscriptionsInGroup(
-                groupUuid, mCallingPackage, mCallingFeature);
-
-        assertTrue(subInfoList.size() > 0);
-        for (SubscriptionInfo info : subInfoList) {
-            assertTrue(info.getIccId().length() > 0);
-            assertTrue(info.getCardString().length() > 0);
-        }
-    }
-
-    private void setNoPermission() {
-        doThrow(new SecurityException()).when(mContext)
-                .enforcePermission(anyString(), anyInt(), anyInt(), anyString());
-    }
-
-    private void setReadPhoneState() {
-        doNothing().when(mContext).enforcePermission(
-                eq(android.Manifest.permission.READ_PHONE_STATE), anyInt(), anyInt(), anyString());
-    }
-
-    @Test
-    @EnableCompatChanges({REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID})
-    public void testGetSubscriptionInGroupWithPhoneNumberAccess() throws Exception {
-        // If the calling package meets any of the requirements for the
-        // LegacyPermissionManager#checkPhoneNumberAccess test then the number should be available
-        // in the SubscriptionInfo.
-        ParcelUuid groupUuid = setupGetSubscriptionsInGroupTest();
-        setupReadPhoneNumbersTest();
-        setPhoneNumberAccess(PackageManager.PERMISSION_GRANTED);
-
-        List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getSubscriptionsInGroup(
-                groupUuid, mCallingPackage, mCallingFeature);
-
-        assertTrue(subInfoList.size() > 0);
-        SubscriptionInfo subInfo = subInfoList.get(0);
-        assertEquals(DISPLAY_NUMBER, subInfo.getNumber());
-    }
-
-    @Test
-    @EnableCompatChanges({REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID})
-    public void testGetSubscriptionsInGroupWithCarrierPrivileges() throws Exception {
-        // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier
-        // privileges the ICC ID should be available in the SubscriptionInfo objects in the List.
-        ParcelUuid groupUuid = setupGetSubscriptionsInGroupTest();
-        setupIdentifierCarrierPrivilegesTest();
-
-        List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getSubscriptionsInGroup(
-                groupUuid, mCallingPackage, mCallingFeature);
-
-        assertTrue(subInfoList.size() > 0);
-        for (SubscriptionInfo info : subInfoList) {
-            assertTrue(info.getIccId().length() > 0);
-            assertTrue(info.getCardString().length() > 0);
-        }
-    }
-
-    @Test
-    @EnableCompatChanges({REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID})
-    public void testGetSubscriptionsInGroupWithPrivilegedPermission() throws Exception {
-        // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier
-        // privileges the ICC ID should be available in the SubscriptionInfo objects in the List.
-        ParcelUuid groupUuid = setupGetSubscriptionsInGroupTest();
-
-        List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getSubscriptionsInGroup(
-                groupUuid, mCallingPackage, mCallingFeature);
-
-        assertTrue(subInfoList.size() > 0);
-        for (SubscriptionInfo info : subInfoList) {
-            assertTrue(info.getIccId().length() > 0);
-            assertTrue(info.getCardString().length() > 0);
-        }
-    }
-
-    private ParcelUuid setupGetSubscriptionsInGroupTest() throws Exception {
-        testInsertSim();
-        int[] subIdList = new int[]{getFirstSubId()};
-        ParcelUuid groupUuid = mSubscriptionControllerUT.createSubscriptionGroup(subIdList,
-                mCallingPackage);
-        assertNotNull(groupUuid);
-        doReturn(subIdList).when(mSubscriptionManager).getCompleteActiveSubscriptionIdList();
-        return groupUuid;
-    }
-
-    private void setupReadPhoneNumbersTest() throws Exception {
-        mSubscriptionControllerUT.setDisplayNumber(DISPLAY_NUMBER, getFirstSubId());
-        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
-        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
-        setupMocksForTelephonyPermissions(Build.VERSION_CODES.R);
-        setPhoneNumberAccess(PackageManager.PERMISSION_DENIED);
-    }
-
-    private void setupIdentifierCarrierPrivilegesTest() throws Exception {
-        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
-        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
-        setupMocksForTelephonyPermissions();
-        setIdentifierAccess(false);
-        setCarrierPrivileges(true);
-    }
-
-    private void setupReadPrivilegePermission() throws Exception {
-        mContextFixture.addCallingOrSelfPermissionToCurrentPermissions(
-                Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
-    }
-    private void removeReadPrivilegePermission() throws Exception {
-        mContextFixture.removeCallingOrSelfPermission(
-                Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
-    }
-
-    private int getFirstSubId() throws Exception {
-        return getSubIdAtIndex(0);
-    }
-
-    private int getSubIdAtIndex(int index) throws Exception {
-        int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibileOnly*/false);
-        assertTrue(subIds != null && subIds.length > index);
-        return subIds[index];
-    }
-
-    @Test
-    public void testGetEnabledSubscriptionIdSingleSIM() {
-        // A single SIM device may have logical slot 0 mapped to physical slot 1
-        // (i.e. logical slot -1 mapped to physical slot 0)
-        UiccSlotInfo slot0 = getFakeUiccSlotInfo(false, -1, null);
-        UiccSlotInfo slot1 = getFakeUiccSlotInfo(true, 0, null);
-        UiccSlotInfo [] uiccSlotInfos = {slot0, slot1};
-        UiccSlot [] uiccSlots = {mUiccSlot, mUiccSlot};
-
-        doReturn(uiccSlotInfos).when(mTelephonyManager).getUiccSlotsInfo();
-        doReturn(uiccSlots).when(mUiccController).getUiccSlots();
-        assertEquals(2, UiccController.getInstance().getUiccSlots().length);
-
-        ContentResolver resolver = mContext.getContentResolver();
-        // logical 0 should find physical 1, has settings enabled subscription 0
-        Settings.Global.putInt(resolver, Settings.Global.ENABLED_SUBSCRIPTION_FOR_SLOT + 1, 0);
-
-        int enabledSubscription = mSubscriptionControllerUT.getEnabledSubscriptionId(0);
-        assertEquals(0, enabledSubscription);
-    }
-
-    @Test
-    public void testGetEnabledSubscriptionIdDualSIM() {
-        doReturn(SINGLE_SIM).when(mTelephonyManager).getSimCount();
-        doReturn(SINGLE_SIM).when(mTelephonyManager).getPhoneCount();
-        doReturn(SINGLE_SIM).when(mTelephonyManager).getActiveModemCount();
-        // A dual SIM device may have logical slot 0 mapped to physical slot 0
-        // (i.e. logical slot 1 mapped to physical slot 1)
-        UiccSlotInfo slot0 = getFakeUiccSlotInfo(true, 0, null);
-        UiccSlotInfo slot1 = getFakeUiccSlotInfo(true, 1, null);
-        UiccSlotInfo [] uiccSlotInfos = {slot0, slot1};
-        UiccSlot [] uiccSlots = {mUiccSlot, mUiccSlot};
-
-        doReturn(2).when(mTelephonyManager).getPhoneCount();
-        doReturn(2).when(mTelephonyManager).getActiveModemCount();
-        doReturn(uiccSlotInfos).when(mTelephonyManager).getUiccSlotsInfo();
-        doReturn(uiccSlots).when(mUiccController).getUiccSlots();
-        assertEquals(2, UiccController.getInstance().getUiccSlots().length);
-
-        ContentResolver resolver = mContext.getContentResolver();
-        // logical 0 should find physical 0, has settings enabled subscription 0
-        Settings.Global.putInt(resolver, Settings.Global.ENABLED_SUBSCRIPTION_FOR_SLOT + 0, 0);
-        Settings.Global.putInt(resolver, Settings.Global.ENABLED_SUBSCRIPTION_FOR_SLOT + 1, 1);
-
-        int enabledSubscription = mSubscriptionControllerUT.getEnabledSubscriptionId(0);
-        int secondEabledSubscription = mSubscriptionControllerUT.getEnabledSubscriptionId(1);
-        assertEquals(0, enabledSubscription);
-        assertEquals(1, secondEabledSubscription);
-    }
-
-
-    private UiccSlotInfo getFakeUiccSlotInfo(boolean active, int logicalSlotIndex, String iccId) {
-        return getFakeUiccSlotInfo(active, logicalSlotIndex, "fake card Id", iccId);
-    }
-
-    private UiccSlotInfo getFakeUiccSlotInfo(
-            boolean active, int logicalSlotIndex, String cardId, String iccId) {
-        return new UiccSlotInfo(false, cardId,
-                UiccSlotInfo.CARD_STATE_INFO_PRESENT, true, true,
-                Collections.singletonList(
-                        new UiccPortInfo(iccId, 0, logicalSlotIndex, active)
-                ));
-    }
-
-    @Test
-    @SmallTest
-    public void testNameSourcePriority() throws Exception {
-        assertTrue(mSubscriptionControllerUT.getNameSourcePriority(
-                SubscriptionManager.NAME_SOURCE_USER_INPUT)
-                > mSubscriptionControllerUT.getNameSourcePriority(
-                        SubscriptionManager.NAME_SOURCE_CARRIER));
-
-        assertTrue(mSubscriptionControllerUT.getNameSourcePriority(
-                SubscriptionManager.NAME_SOURCE_CARRIER)
-                > mSubscriptionControllerUT.getNameSourcePriority(
-                SubscriptionManager.NAME_SOURCE_SIM_SPN));
-
-        assertTrue(mSubscriptionControllerUT.getNameSourcePriority(
-                SubscriptionManager.NAME_SOURCE_SIM_SPN)
-                > mSubscriptionControllerUT.getNameSourcePriority(
-                SubscriptionManager.NAME_SOURCE_SIM_PNN));
-
-        assertTrue(mSubscriptionControllerUT.getNameSourcePriority(
-                SubscriptionManager.NAME_SOURCE_SIM_PNN)
-                > mSubscriptionControllerUT.getNameSourcePriority(
-                SubscriptionManager.NAME_SOURCE_CARRIER_ID));
-    }
-
-    @Test
-    @SmallTest
-    public void testGetAvailableSubscriptionList() throws Exception {
-        // TODO b/123300875 slot index 1 is not expected to be valid
-        mSubscriptionControllerUT.addSubInfo("123", null, 1,
-                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); // sub 1
-        mSubscriptionControllerUT.addSubInfo("456", null, 0,
-                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); // sub 2
-
-        List<SubscriptionInfo> infoList = mSubscriptionControllerUT
-                .getAvailableSubscriptionInfoList(mCallingPackage, mCallingFeature);
-        assertEquals(2, infoList.size());
-        assertEquals("456", infoList.get(0).getIccId());
-        assertEquals("123", infoList.get(1).getIccId());
-
-        // Remove "123" from active sim list but have it inserted.
-        UiccSlot[] uiccSlots = {mUiccSlot};
-        IccCardStatus.CardState cardState = CARDSTATE_PRESENT;
-        doReturn(uiccSlots).when(mUiccController).getUiccSlots();
-        doReturn(cardState).when(mUiccSlot).getCardState();
-        doReturn("123").when(mUiccSlot).getIccId(0); // default port index
-        mSubscriptionControllerUT.clearSubInfoRecord(1);
-
-        // Active sub list should return 1 now.
-        infoList = mSubscriptionControllerUT
-                .getActiveSubscriptionInfoList(mCallingPackage, mCallingFeature);
-        assertEquals(1, infoList.size());
-        assertEquals("456", infoList.get(0).getIccId());
-
-        // Available sub list should still return two.
-        infoList = mSubscriptionControllerUT
-                .getAvailableSubscriptionInfoList(mCallingPackage, mCallingFeature);
-        assertEquals(2, infoList.size());
-        assertEquals("123", infoList.get(0).getIccId());
-        assertEquals("456", infoList.get(1).getIccId());
-    }
-
-    @Test
-    @SmallTest
-    public void testGetAvailableSubscriptionList_withTrailingF() throws Exception {
-        // TODO b/123300875 slot index 1 is not expected to be valid
-        mSubscriptionControllerUT.addSubInfo("123", null, 1,
-                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); // sub 1
-        mSubscriptionControllerUT.addSubInfo("456", null, 0,
-                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); // sub 2
-
-        // Remove "123" from active sim list but have it inserted.
-        UiccSlot[] uiccSlots = {mUiccSlot};
-        IccCardStatus.CardState cardState = CARDSTATE_PRESENT;
-        doReturn(uiccSlots).when(mUiccController).getUiccSlots();
-        doReturn(cardState).when(mUiccSlot).getCardState();
-        // IccId ends with a 'F' which should be ignored and taking into account.
-        doReturn("123F").when(mUiccSlot).getIccId(0); // default port index
-
-        mSubscriptionControllerUT.clearSubInfoRecord(1);
-
-        // Active sub list should return 1 now.
-        List<SubscriptionInfo> infoList = mSubscriptionControllerUT
-                .getActiveSubscriptionInfoList(mCallingPackage, mCallingFeature);
-        assertEquals(1, infoList.size());
-        assertEquals("456", infoList.get(0).getIccId());
-
-        // Available sub list should still return two.
-        infoList = mSubscriptionControllerUT
-                .getAvailableSubscriptionInfoList(mCallingPackage, mCallingFeature);
-        assertEquals(2, infoList.size());
-        assertEquals("123", infoList.get(0).getIccId());
-        assertEquals("456", infoList.get(1).getIccId());
-    }
-
-    @Test
-    @SmallTest
-    public void testSetPreferredDataSubscriptionId_phoneSwitcherNotInitialized() throws Exception {
-        replaceInstance(PhoneSwitcher.class, "sPhoneSwitcher", null, null);
-
-        mSubscriptionControllerUT.setPreferredDataSubscriptionId(1, true, mSetOpptDataCallback);
-        verify(mSetOpptDataCallback).onComplete(SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION);
-    }
-
-    @Test
-    @SmallTest
-    public void testGetPreferredDataSubscriptionId_phoneSwitcherNotInitialized() throws Exception {
-        replaceInstance(PhoneSwitcher.class, "sPhoneSwitcher", null, null);
-
-        assertEquals(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
-                mSubscriptionControllerUT.getPreferredDataSubscriptionId());
-    }
-
-    @Test
-    public void testSetSubscriptionEnabled_disableActivePsim_cardIdWithTrailingF() {
-        String iccId = "123F";
-        mSubscriptionControllerUT.addSubInfo(iccId, null, 0,
-                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
-        mSubscriptionControllerUT.registerForUiccAppsEnabled(mHandler, 0, null, false);
-        UiccSlotInfo slot = getFakeUiccSlotInfo(true, 0, iccId + "FF", iccId);
-        UiccSlotInfo[] uiccSlotInfos = {slot};
-        doReturn(uiccSlotInfos).when(mTelephonyManager).getUiccSlotsInfo();
-
-        mSubscriptionControllerUT.setSubscriptionEnabled(false, 1);
-        verify(mHandler).sendMessageAtTime(any(), anyLong());
-        assertFalse(mSubscriptionControllerUT.getActiveSubscriptionInfo(
-                1, mContext.getOpPackageName(), null).areUiccApplicationsEnabled());
-    }
-
-    @Test
-    @SmallTest
-    public void testInsertEmptySubInfoRecord_returnsNull_ifRecordExists() {
-        final String mockedIccid = "123456789";
-        final int mockedSlotIndex = 1;
-
-        assertNotNull(mSubscriptionControllerUT.insertEmptySubInfoRecord(
-                mockedIccid, mockedSlotIndex));
-        // Insert second time with the same iccid should result in no-op and return null.
-        assertNull(mSubscriptionControllerUT.insertEmptySubInfoRecord(
-                mockedIccid, mockedSlotIndex));
-        assertEquals(
-                1,
-                mSubscriptionControllerUT
-                        .getAllSubInfoList(mCallingPackage, mCallingFeature).size());
-    }
-
-    @Test
-    @SmallTest
-    public void testCheckPhoneIdAndIccIdMatch() {
-        try {
-            testSetSubscriptionGroupWithModifyPermission();
-        } catch (Exception e) {
-            fail("Unexpected exception: " + e);
-        }
-
-        mSubscriptionControllerUT.addSubInfo("test3", null,
-                SubscriptionManager.INVALID_SIM_SLOT_INDEX,
-                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
-
-        assertTrue(mSubscriptionControllerUT.checkPhoneIdAndIccIdMatch(0, "test"));
-        assertTrue(mSubscriptionControllerUT.checkPhoneIdAndIccIdMatch(0, "test2"));
-        assertFalse(mSubscriptionControllerUT.checkPhoneIdAndIccIdMatch(0, "test3"));
-    }
-
-    @Test
-    @SmallTest
-    public void testMessageRefDBFetchAndUpdate() throws Exception {
-        testInsertSim();
-        int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false);
-        assertTrue(subIds != null && subIds.length != 0);
-        final int subId = subIds[0];
-        SubscriptionController.getInstance().updateMessageRef(subId, 201);
-        int messageRef = SubscriptionController.getInstance().getMessageRef(subId);
-        assertTrue("201 :", messageRef == 201);
-    }
-
-    @Test
-    public void setSubscriptionUserHandle_withoutPermission() {
-        testInsertSim();
-        enableGetSubscriptionUserHandle();
-        /* Get SUB ID */
-        int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false);
-        assertTrue(subIds != null && subIds.length != 0);
-        final int subId = subIds[0];
-        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
-
-        assertThrows(SecurityException.class,
-                () -> mSubscriptionControllerUT.setSubscriptionUserHandle(
-                        UserHandle.of(UserHandle.USER_SYSTEM), subId));
-    }
-
-    @Test
-    public void setGetSubscriptionUserHandle_userHandleNull() {
-        testInsertSim();
-        enableGetSubscriptionUserHandle();
-        /* Get SUB ID */
-        int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false);
-        assertTrue(subIds != null && subIds.length != 0);
-        final int subId = subIds[0];
-
-        mSubscriptionControllerUT.setSubscriptionUserHandle(null, subId);
-
-        assertThat(mSubscriptionControllerUT.getSubscriptionUserHandle(subId))
-                .isEqualTo(null);
-    }
-
-    @Test
-    public void setSubscriptionUserHandle_invalidSubId() {
-        enableGetSubscriptionUserHandle();
-
-        assertThrows(IllegalArgumentException.class,
-                () -> mSubscriptionControllerUT.setSubscriptionUserHandle(
-                        UserHandle.of(UserHandle.USER_SYSTEM),
-                        SubscriptionManager.DEFAULT_SUBSCRIPTION_ID));
-    }
-
-    @Test
-    public void setGetSubscriptionUserHandle_withValidUserHandleAndSubId() {
-        testInsertSim();
-        enableGetSubscriptionUserHandle();
-        /* Get SUB ID */
-        int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false);
-        assertTrue(subIds != null && subIds.length != 0);
-        final int subId = subIds[0];
-
-        mSubscriptionControllerUT.setSubscriptionUserHandle(
-                UserHandle.of(UserHandle.USER_SYSTEM), subId);
-
-        assertThat(mSubscriptionControllerUT.getSubscriptionUserHandle(subId))
-                .isEqualTo(UserHandle.of(UserHandle.USER_SYSTEM));
-    }
-
-    @Test
-    public void getSubscriptionUserHandle_withoutPermission() {
-        testInsertSim();
-        enableGetSubscriptionUserHandle();
-        /* Get SUB ID */
-        int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false);
-        assertTrue(subIds != null && subIds.length != 0);
-        final int subId = subIds[0];
-        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
-
-        assertThrows(SecurityException.class,
-                () -> mSubscriptionControllerUT.getSubscriptionUserHandle(subId));
-    }
-
-    @Test
-    public void getSubscriptionUserHandle_invalidSubId() {
-        enableGetSubscriptionUserHandle();
-
-        assertThrows(IllegalArgumentException.class,
-                () -> mSubscriptionControllerUT.getSubscriptionUserHandle(
-                        SubscriptionManager.DEFAULT_SUBSCRIPTION_ID));
-    }
-
-    @Test
-    public void isSubscriptionAssociatedWithUser_withoutPermission() {
-        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
-
-        assertThrows(SecurityException.class,
-                () -> mSubscriptionControllerUT.isSubscriptionAssociatedWithUser(1,
-                        UserHandle.of(UserHandle.USER_SYSTEM)));
-    }
-
-    @Test
-    public void isSubscriptionAssociatedWithUser_noSubscription() {
-        // isSubscriptionAssociatedWithUser should return true if there are no active subscriptions.
-        assertThat(mSubscriptionControllerUT.isSubscriptionAssociatedWithUser(1,
-                UserHandle.of(UserHandle.USER_SYSTEM))).isEqualTo(true);
-    }
-
-    @Test
-    public void isSubscriptionAssociatedWithUser_unknownSubId() {
-        testInsertSim();
-        /* Get SUB ID */
-        int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false);
-        assertTrue(subIds != null && subIds.length != 0);
-        int unknownSubId = 123;
-
-        assertThat(mSubscriptionControllerUT.isSubscriptionAssociatedWithUser(unknownSubId,
-                UserHandle.of(UserHandle.USER_SYSTEM))).isEqualTo(false);
-    }
-
-    @Test
-    public void isSubscriptionAssociatedWithUser_userAssociatedWithSubscription() {
-        testInsertSim();
-        /* Get SUB ID */
-        int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false);
-        assertTrue(subIds != null && subIds.length != 0);
-        final int subId = subIds[0];
-
-        mSubscriptionControllerUT.setSubscriptionUserHandle(
-                UserHandle.of(UserHandle.USER_SYSTEM), subId);
-
-        assertThat(mSubscriptionControllerUT.isSubscriptionAssociatedWithUser(subId,
-                UserHandle.of(UserHandle.USER_SYSTEM))).isEqualTo(true);
-    }
-
-    @Test
-    public void isSubscriptionAssociatedWithUser_userNotAssociatedWithSubscription() {
-        testInsertSim();
-        enableGetSubscriptionUserHandle();
-        /* Get SUB ID */
-        int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false);
-        assertTrue(subIds != null && subIds.length != 0);
-        final int subId = subIds[0];
-
-        mSubscriptionControllerUT.setSubscriptionUserHandle(UserHandle.of(UserHandle.USER_SYSTEM),
-                subId);
-
-        assertThat(mSubscriptionControllerUT.isSubscriptionAssociatedWithUser(subId,
-                UserHandle.of(10))).isEqualTo(false);
-    }
-
-
-    @Test
-    public void getSubscriptionInfoListAssociatedWithUser_withoutPermission() {
-        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
-
-        assertThrows(SecurityException.class,
-                () -> mSubscriptionControllerUT.getSubscriptionInfoListAssociatedWithUser(
-                        UserHandle.of(UserHandle.USER_SYSTEM)));
-    }
-
-    @Test
-    public void getSubscriptionInfoListAssociatedWithUser_noSubscription() {
-        List<SubscriptionInfo> associatedSubInfoList = mSubscriptionControllerUT
-                .getSubscriptionInfoListAssociatedWithUser(UserHandle.of(UserHandle.USER_SYSTEM));
-        assertThat(associatedSubInfoList.size()).isEqualTo(0);
-    }
-
-    private void enableGetSubscriptionUserHandle() {
-        Resources mResources = mock(Resources.class);
-        doReturn(true).when(mResources).getBoolean(
-                eq(com.android.internal.R.bool.config_enable_get_subscription_user_handle));
-        doReturn(mResources).when(mContext).getResources();
-    }
-}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java
deleted file mode 100644
index 32fb56e..0000000
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java
+++ /dev/null
@@ -1,1123 +0,0 @@
-/*
- * Copyright (C) 2016 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 android.telephony.SubscriptionManager.UICC_APPLICATIONS_ENABLED;
-
-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.isNull;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.doReturn;
-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 static org.mockito.Mockito.when;
-
-import android.annotation.Nullable;
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.IPackageManager;
-import android.content.pm.UserInfo;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Looper;
-import android.os.ParcelUuid;
-import android.os.PersistableBundle;
-import android.service.euicc.EuiccProfileInfo;
-import android.service.euicc.EuiccService;
-import android.service.euicc.GetEuiccProfileInfoListResult;
-import android.telephony.CarrierConfigManager;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.telephony.UiccAccessRule;
-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 com.android.internal.telephony.euicc.EuiccController;
-import com.android.internal.telephony.uicc.IccFileHandler;
-import com.android.internal.telephony.uicc.IccRecords;
-import com.android.internal.telephony.uicc.IccUtils;
-import com.android.internal.telephony.uicc.UiccSlot;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class SubscriptionInfoUpdaterTest extends TelephonyTest {
-    private static final int FAKE_SUB_ID_1 = 0;
-    private static final int FAKE_SUB_ID_2 = 1;
-    private static final int FAKE_CARD_ID = 0;
-    private static final String FAKE_EID = "89049032000001000000031328322874";
-    private static final String FAKE_ICCID_1 = "89012604200000000000";
-    private static final String FAKE_MCC_MNC_1 = "123456";
-    private static final String FAKE_MCC_MNC_2 = "456789";
-    private static final int FAKE_PHONE_ID_1 = 0;
-
-    private SubscriptionInfoUpdater mUpdater;
-    private IccRecords mIccRecord;
-
-    // Mocked classes
-    private UserInfo mUserInfo;
-    private SubscriptionInfo mSubInfo;
-    private ContentProvider mContentProvider;
-    private HashMap<String, Object> mSubscriptionContent;
-    private IccFileHandler mIccFileHandler;
-    private EuiccController mEuiccController;
-    private IntentBroadcaster mIntentBroadcaster;
-    private IPackageManager mPackageManager;
-    private UiccSlot mUiccSlot;
-
-    /*Custom ContentProvider */
-    private class FakeSubscriptionContentProvider extends MockContentProvider {
-        @Override
-        public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
-            return mContentProvider.update(uri, values, selection, selectionArgs);
-        }
-
-        @Override
-        public Bundle call(String method, @Nullable String args, @Nullable Bundle bundle) {
-            return new Bundle();
-        }
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        super.setUp(getClass().getSimpleName());
-        enableSubscriptionManagerService(false);
-        mUserInfo = mock(UserInfo.class);
-        mSubInfo = mock(SubscriptionInfo.class);
-        mContentProvider = mock(ContentProvider.class);
-        mSubscriptionContent = mock(HashMap.class);
-        mIccFileHandler = mock(IccFileHandler.class);
-        mEuiccController = mock(EuiccController.class);
-        mIntentBroadcaster = mock(IntentBroadcaster.class);
-        mPackageManager = mock(IPackageManager.class);
-        mUiccSlot = mock(UiccSlot.class);
-
-        replaceInstance(SubscriptionInfoUpdater.class, "sIccId", null, new String[1]);
-        replaceInstance(SubscriptionInfoUpdater.class, "sContext", null, null);
-        replaceInstance(SubscriptionInfoUpdater.class, "SUPPORTED_MODEM_COUNT", null, 1);
-        replaceInstance(SubscriptionInfoUpdater.class, "sSimCardState", null, new int[1]);
-        replaceInstance(SubscriptionInfoUpdater.class, "sSimApplicationState", null, new int[1]);
-        replaceInstance(SubscriptionInfoUpdater.class, "sIsSubInfoInitialized", null, false);
-
-        replaceInstance(EuiccController.class, "sInstance", null, mEuiccController);
-        replaceInstance(IntentBroadcaster.class, "sIntentBroadcaster", null, mIntentBroadcaster);
-
-        doReturn(true).when(mUiccSlot).isActive();
-        doReturn(mUiccSlot).when(mUiccController).getUiccSlotForPhone(anyInt());
-        doReturn(1).when(mTelephonyManager).getSimCount();
-        doReturn(1).when(mTelephonyManager).getPhoneCount();
-        doReturn(1).when(mTelephonyManager).getActiveModemCount();
-
-        when(mContentProvider.update(any(), any(), any(), isNull())).thenAnswer(
-                new Answer<Integer>() {
-                    @Override
-                    public Integer answer(InvocationOnMock invocation) throws Throwable {
-                        ContentValues values = invocation.getArgument(1);
-                        for (String key : values.keySet()) {
-                            mSubscriptionContent.put(key, values.get(key));
-                        }
-                        return 1;
-                    }
-                });
-
-        doReturn(mUserInfo).when(mIActivityManager).getCurrentUser();
-        doReturn(FAKE_SUB_ID_1).when(mSubscriptionController).getSubId(0);
-        doReturn(new int[]{FAKE_SUB_ID_1}).when(mSubscriptionManager).getActiveSubscriptionIdList();
-        ((MockContentResolver) mContext.getContentResolver()).addProvider(
-                SubscriptionManager.CONTENT_URI.getAuthority(),
-                new FakeSubscriptionContentProvider());
-        doReturn(new int[]{}).when(mSubscriptionController)
-                .getActiveSubIdList(/*visibleOnly*/false);
-        mIccRecord = mUiccProfile.getIccRecords();
-
-        mUpdater =
-                new SubscriptionInfoUpdater(Looper.myLooper(), mContext, mSubscriptionController);
-        processAllMessages();
-
-        assertFalse(mUpdater.isSubInfoInitialized());
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        mIccRecord = null;
-        mUpdater = null;
-        super.tearDown();
-    }
-
-    @Test
-    @SmallTest
-    public void testSimAbsent() throws Exception {
-        doReturn(Arrays.asList(mSubInfo)).when(mSubscriptionController)
-                .getSubInfoUsingSlotIndexPrivileged(eq(FAKE_SUB_ID_1));
-        doReturn(new int[]{FAKE_SUB_ID_1}).when(mSubscriptionController)
-                .getActiveSubIdList(/*visibleOnly*/false);
-        mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, FAKE_SUB_ID_1);
-
-        processAllMessages();
-        assertTrue(mUpdater.isSubInfoInitialized());
-        verify(mSubscriptionController, times(1)).clearSubInfoRecord(eq(FAKE_SUB_ID_1));
-
-        CarrierConfigManager mConfigManager = (CarrierConfigManager)
-                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        verify(mConfigManager).updateConfigForPhoneId(eq(FAKE_SUB_ID_1),
-                eq(IccCardConstants.INTENT_VALUE_ICC_ABSENT));
-        verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged();
-    }
-
-    @Test
-    @SmallTest
-    public void testSimAbsentAndInactive() throws Exception {
-        doReturn(Arrays.asList(mSubInfo)).when(mSubscriptionController)
-                .getSubInfoUsingSlotIndexPrivileged(eq(FAKE_SUB_ID_1));
-        doReturn(new int[]{FAKE_SUB_ID_1}).when(mSubscriptionController)
-                .getActiveSubIdList(/*visibleOnly*/false);
-        mUpdater.updateInternalIccStateForInactivePort(FAKE_SUB_ID_1, null);
-
-        processAllMessages();
-        assertTrue(mUpdater.isSubInfoInitialized());
-        verify(mSubscriptionController, times(1)).clearSubInfoRecord(eq(FAKE_SUB_ID_1));
-
-        // Verify that in the special absent and inactive case, we update subscriptions without
-        // broadcasting SIM state change
-        CarrierConfigManager mConfigManager = (CarrierConfigManager)
-                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        verify(mConfigManager, times(0)).updateConfigForPhoneId(eq(FAKE_SUB_ID_1),
-                eq(IccCardConstants.INTENT_VALUE_ICC_ABSENT));
-        verify(mContext, times(0)).sendBroadcast(any(), anyString());
-        verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged();
-    }
-
-    @Test
-    @SmallTest
-    public void testSimUnknown() throws Exception {
-        mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_UNKNOWN, null, FAKE_SUB_ID_1);
-
-        processAllMessages();
-        assertFalse(mUpdater.isSubInfoInitialized());
-        verify(mSubscriptionContent, times(0)).put(anyString(), any());
-        CarrierConfigManager mConfigManager = (CarrierConfigManager)
-                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        verify(mConfigManager).updateConfigForPhoneId(eq(FAKE_SUB_ID_1),
-                eq(IccCardConstants.INTENT_VALUE_ICC_UNKNOWN));
-        verify(mSubscriptionController, times(0)).clearSubInfo();
-        verify(mSubscriptionController, times(0)).notifySubscriptionInfoChanged();
-    }
-
-    @Test
-    @SmallTest
-    public void testSimNotReady() throws Exception {
-        mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_NOT_READY, null, FAKE_PHONE_ID_1);
-
-        processAllMessages();
-        assertFalse(mUpdater.isSubInfoInitialized());
-        verify(mSubscriptionContent, never()).put(anyString(), any());
-        CarrierConfigManager mConfigManager = (CarrierConfigManager)
-                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        verify(mConfigManager, never()).updateConfigForPhoneId(eq(FAKE_PHONE_ID_1),
-                eq(IccCardConstants.INTENT_VALUE_ICC_NOT_READY));
-        verify(mSubscriptionController, never()).clearSubInfoRecord(FAKE_PHONE_ID_1);
-        verify(mSubscriptionController, never()).notifySubscriptionInfoChanged();
-    }
-
-    @Test
-    @SmallTest
-    public void testSimNotReadyEmptyProfile() throws Exception {
-        doReturn(mIccCard).when(mPhone).getIccCard();
-        doReturn(true).when(mIccCard).isEmptyProfile();
-
-        mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_NOT_READY, null, FAKE_PHONE_ID_1);
-
-        processAllMessages();
-        assertTrue(mUpdater.isSubInfoInitialized());
-        // Sub info should be cleared and change should be notified.
-        verify(mSubscriptionController).clearSubInfoRecord(eq(FAKE_PHONE_ID_1));
-        verify(mSubscriptionController).notifySubscriptionInfoChanged();
-        // No new sub should be added.
-        verify(mSubscriptionManager, never()).addSubscriptionInfoRecord(any(), anyInt());
-        verify(mSubscriptionContent, never()).put(anyString(), any());
-        CarrierConfigManager mConfigManager = (CarrierConfigManager)
-                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        verify(mConfigManager).updateConfigForPhoneId(eq(FAKE_PHONE_ID_1),
-                eq(IccCardConstants.INTENT_VALUE_ICC_NOT_READY));
-    }
-
-    @Test
-    @SmallTest
-    public void testSimNotReadyDisabledUiccApps() throws Exception {
-        String iccId = "123456";
-        doReturn(mIccCard).when(mPhone).getIccCard();
-        doReturn(false).when(mIccCard).isEmptyProfile();
-        doReturn(mUiccPort).when(mUiccController).getUiccPort(anyInt());
-        doReturn(iccId).when(mUiccPort).getIccId();
-        doReturn(mSubInfo).when(mSubscriptionController).getSubInfoForIccId(iccId);
-        doReturn(false).when(mSubInfo).areUiccApplicationsEnabled();
-
-        mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_NOT_READY, null, FAKE_PHONE_ID_1);
-
-        processAllMessages();
-        assertTrue(mUpdater.isSubInfoInitialized());
-        // Sub info should be cleared and change should be notified.
-        verify(mSubscriptionController).clearSubInfoRecord(eq(FAKE_PHONE_ID_1));
-        verify(mSubscriptionController).notifySubscriptionInfoChanged();
-        // No new sub should be added.
-        verify(mSubscriptionManager, never()).addSubscriptionInfoRecord(any(), anyInt());
-        verify(mSubscriptionContent, never()).put(anyString(), any());
-        CarrierConfigManager mConfigManager = (CarrierConfigManager)
-                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        verify(mConfigManager).updateConfigForPhoneId(eq(FAKE_PHONE_ID_1),
-                eq(IccCardConstants.INTENT_VALUE_ICC_NOT_READY));
-
-        // When becomes ABSENT, UICC_APPLICATIONS_ENABLED should be reset to true.
-        mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, FAKE_PHONE_ID_1);
-        processAllMessages();
-        ArgumentCaptor<ContentValues> valueCapture = ArgumentCaptor.forClass(ContentValues.class);
-        verify(mContentProvider).update(eq(SubscriptionManager.CONTENT_URI), valueCapture.capture(),
-                eq(SubscriptionManager.ICC_ID + "=\'" + iccId + "\'"), eq(null));
-        ContentValues contentValues = valueCapture.getValue();
-        assertTrue(contentValues != null && contentValues.getAsBoolean(
-                UICC_APPLICATIONS_ENABLED));
-    }
-
-    @Test
-    @SmallTest
-    public void testSimRemovedWhileDisablingUiccApps() throws Exception {
-        loadSim();
-
-        mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, FAKE_SUB_ID_1);
-        processAllMessages();
-
-        // UICC_APPLICATIONS_ENABLED should be reset to true.
-        ArgumentCaptor<ContentValues> valueCapture = ArgumentCaptor.forClass(ContentValues.class);
-        verify(mContentProvider).update(eq(SubscriptionManager.CONTENT_URI), valueCapture.capture(),
-                eq(SubscriptionManager.ICC_ID + "=\'" + FAKE_ICCID_1 + "\'"), eq(null));
-        ContentValues contentValues = valueCapture.getValue();
-        assertTrue(contentValues != null && contentValues.getAsBoolean(
-                UICC_APPLICATIONS_ENABLED));
-    }
-
-    @Test
-    @SmallTest
-    public void testSimError() throws Exception {
-        mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR, null, FAKE_SUB_ID_1);
-
-        processAllMessages();
-        assertTrue(mUpdater.isSubInfoInitialized());
-        verify(mSubscriptionContent, times(0)).put(anyString(), any());
-        CarrierConfigManager mConfigManager = (CarrierConfigManager)
-                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        verify(mConfigManager).updateConfigForPhoneId(eq(FAKE_SUB_ID_1),
-                eq(IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR));
-        verify(mSubscriptionController, times(0)).clearSubInfo();
-        verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged();
-    }
-
-    @Test
-    @SmallTest
-    public void testWrongSimState() throws Exception {
-        mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_IMSI, null, 2);
-
-        processAllMessages();
-        assertFalse(mUpdater.isSubInfoInitialized());
-        verify(mSubscriptionContent, times(0)).put(anyString(), any());
-        CarrierConfigManager mConfigManager = (CarrierConfigManager)
-                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        verify(mConfigManager, times(0)).updateConfigForPhoneId(eq(2),
-                eq(IccCardConstants.INTENT_VALUE_ICC_IMSI));
-        verify(mSubscriptionController, times(0)).clearSubInfo();
-        verify(mSubscriptionController, times(0)).notifySubscriptionInfoChanged();
-    }
-
-    private void loadSim() {
-        doReturn(FAKE_SUB_ID_1).when(mSubInfo).getSubscriptionId();
-        doReturn(Arrays.asList(mSubInfo)).when(mSubscriptionController)
-                .getSubInfoUsingSlotIndexPrivileged(eq(FAKE_SUB_ID_1));
-        doReturn(FAKE_ICCID_1).when(mIccRecord).getFullIccId();
-        doReturn(FAKE_MCC_MNC_1).when(mTelephonyManager).getSimOperatorNumeric(FAKE_SUB_ID_1);
-        when(mActivityManager.updateMccMncConfiguration(anyString(), anyString())).thenReturn(
-                true);
-
-        mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_LOADED, null, FAKE_SUB_ID_1);
-
-        processAllMessages();
-        assertTrue(mUpdater.isSubInfoInitialized());
-
-        CarrierConfigManager mConfigManager = (CarrierConfigManager)
-                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        verify(mConfigManager).updateConfigForPhoneId(eq(FAKE_SUB_ID_1),
-                eq(IccCardConstants.INTENT_VALUE_ICC_LOADED));
-    }
-
-    @Test
-    @SmallTest
-    public void testSimLoaded() throws Exception {
-        loadSim();
-
-        // verify SIM_STATE_CHANGED broadcast. It should be broadcast twice, once for
-        // READ_PHONE_STATE and once for READ_PRIVILEGED_PHONE_STATE
-        /* todo: cannot verify as intent is sent using ActivityManagerNative.broadcastStickyIntent()
-         * uncomment code below when that is fixed
-         */
-        /* ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
-        ArgumentCaptor<String> stringArgumentCaptor = ArgumentCaptor.forClass(String.class);
-        verify(mContext, times(2)).sendBroadcast(intentArgumentCaptor.capture(),
-                stringArgumentCaptor.capture());
-        assertEquals(TelephonyIntents.ACTION_SIM_STATE_CHANGED,
-                intentArgumentCaptor.getAllValues().get(0).getAction());
-        assertEquals(Manifest.permission.READ_PHONE_STATE,
-                stringArgumentCaptor.getAllValues().get(0));
-        assertEquals(TelephonyIntents.ACTION_SIM_STATE_CHANGED,
-                intentArgumentCaptor.getAllValues().get(1).getAction());
-        assertEquals(Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
-                stringArgumentCaptor.getAllValues().get(1)); */
-
-        SubscriptionManager mSubscriptionManager = SubscriptionManager.from(mContext);
-        verify(mTelephonyManager).getSimOperatorNumeric(FAKE_SUB_ID_1);
-        verify(mSubscriptionManager, times(1)).addSubscriptionInfoRecord(
-                eq(FAKE_ICCID_1), eq(FAKE_SUB_ID_1));
-        verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged();
-        verify(mSubscriptionController, times(1)).setMccMnc(FAKE_MCC_MNC_1, FAKE_SUB_ID_1);
-        verify(mSubscriptionController, times(0)).clearSubInfo();
-        CarrierConfigManager mConfigManager = (CarrierConfigManager)
-                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        verify(mConfigManager, times(1)).updateConfigForPhoneId(eq(FAKE_SUB_ID_1),
-                eq(IccCardConstants.INTENT_VALUE_ICC_LOADED));
-
-        // ACTION_USER_UNLOCKED should trigger another SIM_STATE_CHANGED
-        Intent intentSimStateChanged = new Intent(Intent.ACTION_USER_UNLOCKED);
-        mContext.sendBroadcast(intentSimStateChanged);
-        processAllMessages();
-
-        // verify SIM_STATE_CHANGED broadcast
-        /* todo: cannot verify as intent is sent using ActivityManagerNative.broadcastStickyIntent()
-         * uncomment code below when that is fixed
-         */
-        /* verify(mContext, times(4)).sendBroadcast(intentArgumentCaptor.capture(),
-                stringArgumentCaptor.capture());
-        assertEquals(TelephonyIntents.ACTION_SIM_STATE_CHANGED,
-                intentArgumentCaptor.getAllValues().get(2).getAction());
-        assertEquals(Manifest.permission.READ_PHONE_STATE,
-                stringArgumentCaptor.getAllValues().get(2));
-        assertEquals(TelephonyIntents.ACTION_SIM_STATE_CHANGED,
-                intentArgumentCaptor.getAllValues().get(3).getAction());
-        assertEquals(Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
-                stringArgumentCaptor.getAllValues().get(3)); */
-    }
-
-    @Test
-    @SmallTest
-    public void testSimLoadedEmptyOperatorNumeric() throws Exception {
-        doReturn(FAKE_ICCID_1).when(mIccRecord).getFullIccId();
-        // operator numeric is empty
-        doReturn("").when(mTelephonyManager).getSimOperatorNumeric(FAKE_SUB_ID_1);
-        doReturn(FAKE_SUB_ID_1).when(mSubInfo).getSubscriptionId();
-        doReturn(Arrays.asList(mSubInfo)).when(mSubscriptionController)
-                .getSubInfoUsingSlotIndexPrivileged(eq(FAKE_SUB_ID_1));
-        mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_LOADED, null, FAKE_SUB_ID_1);
-
-        processAllMessages();
-        assertTrue(mUpdater.isSubInfoInitialized());
-        SubscriptionManager mSubscriptionManager = SubscriptionManager.from(mContext);
-        verify(mTelephonyManager).getSimOperatorNumeric(FAKE_SUB_ID_1);
-        verify(mSubscriptionManager, times(1)).addSubscriptionInfoRecord(
-                eq(FAKE_ICCID_1), eq(FAKE_SUB_ID_1));
-        verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged();
-        verify(mSubscriptionController, times(0)).setMccMnc(anyString(), anyInt());
-        verify(mSubscriptionController, times(0)).clearSubInfo();
-        CarrierConfigManager mConfigManager = (CarrierConfigManager)
-                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        verify(mConfigManager, times(1)).updateConfigForPhoneId(eq(FAKE_SUB_ID_1),
-                eq(IccCardConstants.INTENT_VALUE_ICC_LOADED));
-    }
-
-    @Test
-    @SmallTest
-    public void testSimLockedWithOutIccId() throws Exception {
-        /* mock no IccId Info present and try to query IccId
-         after IccId query, update subscriptionDB */
-        doReturn("98106240020000000000").when(mIccRecord).getFullIccId();
-
-        doReturn(Arrays.asList(mSubInfo)).when(mSubscriptionController)
-                .getSubInfoUsingSlotIndexPrivileged(eq(FAKE_SUB_ID_1));
-        mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_LOCKED, "TESTING", FAKE_SUB_ID_1);
-
-        processAllMessages();
-        assertTrue(mUpdater.isSubInfoInitialized());
-        SubscriptionManager mSubscriptionManager = SubscriptionManager.from(mContext);
-        verify(mSubscriptionManager, times(1)).addSubscriptionInfoRecord(
-                eq("98106240020000000000"), eq(FAKE_SUB_ID_1));
-
-        verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged();
-        verify(mSubscriptionController, times(0)).clearSubInfo();
-        CarrierConfigManager mConfigManager = (CarrierConfigManager)
-                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        verify(mConfigManager, times(1)).updateConfigForPhoneId(eq(FAKE_SUB_ID_1),
-                eq(IccCardConstants.INTENT_VALUE_ICC_LOCKED));
-    }
-
-    @Test
-    @SmallTest
-    public void testDualSimLoaded() throws Exception {
-        // Mock there is two sim cards
-        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[]{mPhone, mPhone});
-        replaceInstance(SubscriptionInfoUpdater.class, "sIccId", null,
-                new String[]{null, null});
-        replaceInstance(SubscriptionInfoUpdater.class, "SUPPORTED_MODEM_COUNT", null, 2);
-        replaceInstance(SubscriptionInfoUpdater.class, "sSimCardState", null,
-                new int[]{0, 0});
-        replaceInstance(SubscriptionInfoUpdater.class, "sSimApplicationState", null,
-                new int[]{0, 0});
-
-        doReturn(new int[]{FAKE_SUB_ID_1, FAKE_SUB_ID_2}).when(mSubscriptionManager)
-                .getActiveSubscriptionIdList();
-        doReturn(FAKE_SUB_ID_1).when(mSubscriptionController).getPhoneId(eq(FAKE_SUB_ID_1));
-        doReturn(FAKE_SUB_ID_2).when(mSubscriptionController).getPhoneId(eq(FAKE_SUB_ID_2));
-        doReturn(2).when(mTelephonyManager).getPhoneCount();
-        doReturn(2).when(mTelephonyManager).getActiveModemCount();
-        when(mActivityManager.updateMccMncConfiguration(anyString(), anyString())).thenReturn(
-                true);
-        doReturn(FAKE_MCC_MNC_1).when(mTelephonyManager).getSimOperatorNumeric(eq(FAKE_SUB_ID_1));
-        doReturn(FAKE_MCC_MNC_2).when(mTelephonyManager).getSimOperatorNumeric(eq(FAKE_SUB_ID_2));
-        verify(mSubscriptionController, times(0)).clearSubInfo();
-        doReturn(FAKE_ICCID_1).when(mIccRecord).getFullIccId();
-        SubscriptionManager mSubscriptionManager = SubscriptionManager.from(mContext);
-        verify(mSubscriptionManager, times(0)).addSubscriptionInfoRecord(anyString(), anyInt());
-        verify(mSubscriptionController, times(0)).notifySubscriptionInfoChanged();
-        verify(mSubscriptionController, times(0)).setMccMnc(anyString(), anyInt());
-
-        // Mock sending a sim loaded for SIM 1
-        doReturn(Arrays.asList(mSubInfo)).when(mSubscriptionController)
-                .getSubInfoUsingSlotIndexPrivileged(eq(FAKE_SUB_ID_1));
-        mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_LOADED, null, FAKE_SUB_ID_1);
-
-        processAllMessages();
-        verify(mSubscriptionManager, times(1)).addSubscriptionInfoRecord(anyString(), anyInt());
-        verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged();
-        verify(mSubscriptionController, times(1)).setMccMnc(anyString(), anyInt());
-        assertFalse(mUpdater.isSubInfoInitialized());
-
-        // Mock sending a sim loaded for SIM 2
-        doReturn(Arrays.asList(mSubInfo)).when(mSubscriptionController)
-                .getSubInfoUsingSlotIndexPrivileged(eq(FAKE_SUB_ID_2));
-        doReturn(FAKE_SUB_ID_2).when(mSubInfo).getSubscriptionId();
-        doReturn("89012604200000000001").when(mIccRecord).getFullIccId();
-
-        mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_LOADED, null, FAKE_SUB_ID_2);
-
-        processAllMessages();
-        verify(mSubscriptionManager, times(1)).addSubscriptionInfoRecord(eq(FAKE_ICCID_1),
-                eq(FAKE_SUB_ID_1));
-        verify(mSubscriptionManager, times(1)).addSubscriptionInfoRecord(eq("89012604200000000001"),
-                eq(FAKE_SUB_ID_2));
-        verify(mSubscriptionController, times(1)).setMccMnc(eq(FAKE_MCC_MNC_1), eq(FAKE_SUB_ID_1));
-        verify(mSubscriptionController, times(1)).setMccMnc(eq(FAKE_MCC_MNC_2), eq(FAKE_SUB_ID_2));
-        verify(mSubscriptionController, times(2)).notifySubscriptionInfoChanged();
-        assertTrue(mUpdater.isSubInfoInitialized());
-    }
-
-    @Test
-    @SmallTest
-    public void testSimLockWithIccId() throws Exception {
-        // ICCID will be queried even if it is already available
-        doReturn("98106240020000000000").when(mIccRecord).getFullIccId();
-
-        replaceInstance(SubscriptionInfoUpdater.class, "sIccId", null,
-                new String[]{FAKE_ICCID_1});
-
-        mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_LOCKED, "TESTING", FAKE_SUB_ID_1);
-
-        processAllMessages();
-        assertTrue(mUpdater.isSubInfoInitialized());
-        SubscriptionManager mSubscriptionManager = SubscriptionManager.from(mContext);
-        verify(mSubscriptionManager, times(1)).addSubscriptionInfoRecord(
-                anyString(), eq(FAKE_SUB_ID_1));
-        verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged();
-        verify(mSubscriptionController, times(0)).clearSubInfo();
-        CarrierConfigManager mConfigManager = (CarrierConfigManager)
-                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        /* broadcast is done */
-        verify(mConfigManager, times(1)).updateConfigForPhoneId(eq(FAKE_SUB_ID_1),
-                eq(IccCardConstants.INTENT_VALUE_ICC_LOCKED));
-    }
-
-    @Test
-    @SmallTest
-    public void testUpdateEmbeddedSubscriptions_listSuccess() throws Exception {
-        when(mEuiccManager.isEnabled()).thenReturn(true);
-        when(mEuiccManager.createForCardId(anyInt())).thenReturn(mEuiccManager);
-        when(mEuiccManager.getEid()).thenReturn(FAKE_EID);
-
-        EuiccProfileInfo[] euiccProfiles = new EuiccProfileInfo[] {
-                new EuiccProfileInfo("1", null /* accessRules */, null /* nickname */),
-                new EuiccProfileInfo("3", null /* accessRules */, null /* nickname */),
-        };
-        when(mEuiccController.blockingGetEuiccProfileInfoList(FAKE_CARD_ID)).thenReturn(
-                new GetEuiccProfileInfoListResult(
-                        EuiccService.RESULT_OK, euiccProfiles, false /* removable */));
-
-        List<SubscriptionInfo> subInfoList = new ArrayList<>();
-        // 1: not embedded, but has matching iccid with an embedded subscription.
-        subInfoList.add(new SubscriptionInfo.Builder()
-                .setSimSlotIndex(0)
-                .setIccId("1")
-                .build());
-        // 2: embedded but no longer present.
-        subInfoList.add(new SubscriptionInfo.Builder()
-                .setSimSlotIndex(0)
-                .setIccId("2")
-                .setEmbedded(true)
-                .build());
-
-        when(mSubscriptionController.getSubscriptionInfoListForEmbeddedSubscriptionUpdate(
-                new String[] { "1", "3"}, false /* removable */)).thenReturn(subInfoList);
-
-        List<Integer> cardIds = new ArrayList<>();
-        cardIds.add(FAKE_CARD_ID);
-        mUpdater.updateEmbeddedSubscriptions(cardIds, null /* callback */);
-        processAllMessages();
-
-        // 3 is new and so a new entry should have been created.
-        verify(mSubscriptionController).insertEmptySubInfoRecord(
-                "3", SubscriptionManager.SIM_NOT_INSERTED);
-        // 1 already existed, so no new entries should be created for it.
-        verify(mSubscriptionController, times(0)).clearSubInfo();
-        verify(mSubscriptionController, never()).insertEmptySubInfoRecord(eq("1"), anyInt());
-
-        // Info for 1 and 3 should be updated as active embedded subscriptions.
-        ArgumentCaptor<ContentValues> iccid1Values = ArgumentCaptor.forClass(ContentValues.class);
-        verify(mContentProvider).update(eq(SubscriptionManager.CONTENT_URI), iccid1Values.capture(),
-                eq(SubscriptionManager.ICC_ID + "='1'"), isNull());
-        assertEquals(1,
-                iccid1Values.getValue().getAsInteger(SubscriptionManager.IS_EMBEDDED).intValue());
-        ArgumentCaptor<ContentValues> iccid3Values = ArgumentCaptor.forClass(ContentValues.class);
-        verify(mContentProvider).update(eq(SubscriptionManager.CONTENT_URI), iccid3Values.capture(),
-                eq(SubscriptionManager.ICC_ID + "='3'"), isNull());
-        assertEquals(1,
-                iccid3Values.getValue().getAsInteger(SubscriptionManager.IS_EMBEDDED).intValue());
-
-        // 2 should have been removed since it was returned from the cache but was not present
-        // in the list provided by the LPA.
-        ArgumentCaptor<ContentValues> iccid2Values = ArgumentCaptor.forClass(ContentValues.class);
-        verify(mContentProvider).update(eq(SubscriptionManager.CONTENT_URI), iccid2Values.capture(),
-                eq(SubscriptionManager.ICC_ID + " IN ('2')"), isNull());
-        assertEquals(0,
-                iccid2Values.getValue().getAsInteger(SubscriptionManager.IS_EMBEDDED).intValue());
-    }
-
-    @Test
-    @SmallTest
-    public void testUpdateEmbeddedSubscriptions_listFailure() throws Exception {
-        when(mEuiccManager.isEnabled()).thenReturn(true);
-        when(mEuiccController.blockingGetEuiccProfileInfoList(FAKE_CARD_ID))
-                .thenReturn(new GetEuiccProfileInfoListResult(
-                        42, null /* subscriptions */, false /* removable */));
-
-        List<SubscriptionInfo> subInfoList = new ArrayList<>();
-        // 1: not embedded, but has matching iccid with an embedded subscription.
-        subInfoList.add(new SubscriptionInfo.Builder()
-                .setSimSlotIndex(0)
-                .setIccId("1")
-                .build());
-        // 2: embedded.
-        subInfoList.add(new SubscriptionInfo.Builder()
-                .setSimSlotIndex(0)
-                .setIccId("2")
-                .setEmbedded(true)
-                .build());
-
-        when(mSubscriptionController.getSubscriptionInfoListForEmbeddedSubscriptionUpdate(
-                new String[0], false /* removable */)).thenReturn(subInfoList);
-
-        ArrayList<Integer> cardIds = new ArrayList<>(1);
-        cardIds.add(FAKE_CARD_ID);
-        mUpdater.updateEmbeddedSubscriptions(cardIds, null /* callback */);
-
-        // No new entries should be created.
-        verify(mSubscriptionController, times(0)).clearSubInfo();
-        verify(mSubscriptionController, never()).insertEmptySubInfoRecord(anyString(), anyInt());
-
-        // No existing entries should have been updated.
-        verify(mContentProvider, never()).update(eq(SubscriptionManager.CONTENT_URI), any(),
-                any(), isNull());
-    }
-
-    @Test
-    @SmallTest
-    public void testUpdateEmbeddedSubscriptions_emptyToEmpty() throws Exception {
-        when(mEuiccManager.isEnabled()).thenReturn(true);
-        when(mEuiccController.blockingGetEuiccProfileInfoList(FAKE_CARD_ID))
-                .thenReturn(new GetEuiccProfileInfoListResult(
-                        42, null /* subscriptions */, true /* removable */));
-
-        List<SubscriptionInfo> subInfoList = new ArrayList<>();
-        // 1: not embedded.
-        subInfoList.add(new SubscriptionInfo.Builder()
-                .setSimSlotIndex(0)
-                .setIccId("1")
-                .build());
-
-        when(mSubscriptionController.getSubscriptionInfoListForEmbeddedSubscriptionUpdate(
-                new String[0], false /* removable */)).thenReturn(subInfoList);
-
-        ArrayList<Integer> cardIds = new ArrayList<>(1);
-        cardIds.add(FAKE_CARD_ID);
-        mUpdater.updateEmbeddedSubscriptions(cardIds, null /* callback */);
-
-        // No new entries should be created.
-        verify(mSubscriptionController, never()).insertEmptySubInfoRecord(anyString(), anyInt());
-
-        // No existing entries should have been updated.
-        verify(mContentProvider, never()).update(eq(SubscriptionManager.CONTENT_URI), any(),
-                any(), isNull());
-    }
-
-    @Test
-    @SmallTest
-    public void testHexIccIdSuffix() throws Exception {
-        doReturn(null).when(mSubscriptionController)
-                .getSubInfoUsingSlotIndexPrivileged(anyInt());
-        verify(mSubscriptionController, times(0)).clearSubInfo();
-        doReturn("890126042000000000Ff").when(mIccRecord).getFullIccId();
-
-        // Mock sending a sim loaded for SIM 1
-        mUpdater.updateInternalIccState(
-                IccCardConstants.INTENT_VALUE_ICC_LOADED, "TESTING", FAKE_SUB_ID_1);
-
-        processAllMessages();
-
-        SubscriptionManager mSubscriptionManager = SubscriptionManager.from(mContext);
-        verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged();
-        verify(mSubscriptionManager, times(1)).addSubscriptionInfoRecord(eq("890126042000000000"),
-                eq(FAKE_SUB_ID_1));
-        verify(mSubscriptionController, times(0)).clearSubInfo();
-    }
-
-    PersistableBundle getCarrierConfigForSubInfoUpdate(
-            boolean isOpportunistic, String groupUuid) {
-        PersistableBundle p = new PersistableBundle();
-        p.putBoolean(CarrierConfigManager.KEY_IS_OPPORTUNISTIC_SUBSCRIPTION_BOOL, isOpportunistic);
-        p.putString(CarrierConfigManager.KEY_SUBSCRIPTION_GROUP_UUID_STRING, groupUuid);
-        return p;
-    }
-
-    @Test
-    @SmallTest
-    public void testUpdateFromCarrierConfigOpportunisticUnchanged() throws Exception {
-        final int phoneId = mPhone.getPhoneId();
-        String carrierPackageName = "FakeCarrierPackageName";
-
-        doReturn(FAKE_SUB_ID_1).when(mSubscriptionController).getSubId(phoneId);
-        doReturn(mSubInfo).when(mSubscriptionController).getSubscriptionInfo(eq(FAKE_SUB_ID_1));
-        doReturn(carrierPackageName).when(mTelephonyManager)
-                .getCarrierServicePackageNameForLogicalSlot(eq(phoneId));
-        ((MockContentResolver) mContext.getContentResolver()).addProvider(
-                SubscriptionManager.CONTENT_URI.getAuthority(),
-                new FakeSubscriptionContentProvider());
-
-        mUpdater.updateSubscriptionByCarrierConfig(mPhone.getPhoneId(),
-                carrierPackageName, new PersistableBundle());
-
-        //at each call to updateSubscriptionByCarrierConfig, only carrier certs are updated
-        verify(mContentProvider, times(1)).update(any(), any(), any(), any());
-        verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged();
-        verify(mSubscriptionController, times(1)).refreshCachedActiveSubscriptionInfoList();
-    }
-
-    @Test
-    @SmallTest
-    public void testUpdateFromCarrierConfigOpportunisticSetOpportunistic() throws Exception {
-        final int phoneId = mPhone.getPhoneId();
-        PersistableBundle carrierConfig = getCarrierConfigForSubInfoUpdate(
-                true, "");
-        String carrierPackageName = "FakeCarrierPackageName";
-
-        doReturn(FAKE_SUB_ID_1).when(mSubscriptionController).getSubId(phoneId);
-        doReturn(mSubInfo).when(mSubscriptionController).getSubscriptionInfo(eq(FAKE_SUB_ID_1));
-        doReturn(false).when(mSubInfo).isOpportunistic();
-        doReturn(carrierPackageName).when(mTelephonyManager)
-                .getCarrierServicePackageNameForLogicalSlot(eq(phoneId));
-        ((MockContentResolver) mContext.getContentResolver()).addProvider(
-                SubscriptionManager.CONTENT_URI.getAuthority(),
-                new FakeSubscriptionContentProvider());
-
-        mUpdater.updateSubscriptionByCarrierConfig(mPhone.getPhoneId(),
-                carrierPackageName, carrierConfig);
-
-        ArgumentCaptor<ContentValues> cvCaptor = ArgumentCaptor.forClass(ContentValues.class);
-        verify(mContentProvider, times(1)).update(
-                eq(SubscriptionManager.getUriForSubscriptionId(FAKE_SUB_ID_1)),
-                cvCaptor.capture(), eq(null), eq(null));
-        assertEquals(1, cvCaptor.getValue().getAsInteger(
-                SubscriptionManager.IS_OPPORTUNISTIC).intValue());
-        // 2 updates: isOpportunistic, and carrier certs:
-        assertEquals(2, cvCaptor.getValue().size());
-        verify(mSubscriptionController, times(1)).refreshCachedActiveSubscriptionInfoList();
-        verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged();
-    }
-
-    @Test
-    @SmallTest
-    public void testOpportunisticSubscriptionNotUnsetWithEmptyConfigKey() throws Exception {
-        final int phoneId = mPhone.getPhoneId();
-        PersistableBundle carrierConfig = new PersistableBundle();
-
-        String carrierPackageName = "FakeCarrierPackageName";
-
-        doReturn(FAKE_SUB_ID_1).when(mSubscriptionController).getSubId(phoneId);
-        doReturn(mSubInfo).when(mSubscriptionController).getSubscriptionInfo(eq(FAKE_SUB_ID_1));
-        doReturn(true).when(mSubInfo).isOpportunistic();
-        doReturn(carrierPackageName).when(mTelephonyManager)
-                .getCarrierServicePackageNameForLogicalSlot(eq(phoneId));
-        ((MockContentResolver) mContext.getContentResolver()).addProvider(
-                SubscriptionManager.CONTENT_URI.getAuthority(),
-                new FakeSubscriptionContentProvider());
-
-        mUpdater.updateSubscriptionByCarrierConfig(mPhone.getPhoneId(),
-                carrierPackageName, carrierConfig);
-
-        ArgumentCaptor<ContentValues> cvCaptor = ArgumentCaptor.forClass(ContentValues.class);
-        verify(mContentProvider, times(1)).update(
-                eq(SubscriptionManager.getUriForSubscriptionId(FAKE_SUB_ID_1)),
-                cvCaptor.capture(), eq(null), eq(null));
-        // no key is added for the opportunistic bit
-        assertNull(cvCaptor.getValue().getAsInteger(SubscriptionManager.IS_OPPORTUNISTIC));
-        // only carrier certs updated
-        assertEquals(1, cvCaptor.getValue().size());
-        verify(mSubscriptionController, times(1)).refreshCachedActiveSubscriptionInfoList();
-        verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged();
-    }
-
-    @Test
-    @SmallTest
-    public void testUpdateFromCarrierConfigOpportunisticAddToGroup() throws Exception {
-        final int phoneId = mPhone.getPhoneId();
-        PersistableBundle carrierConfig = getCarrierConfigForSubInfoUpdate(
-                true, "11111111-2222-3333-4444-555555555555");
-        String carrierPackageName = "FakeCarrierPackageName";
-
-        doReturn(true).when(mSubscriptionController).canPackageManageGroup(
-                ParcelUuid.fromString("11111111-2222-3333-4444-555555555555"), carrierPackageName);
-        doReturn(FAKE_SUB_ID_1).when(mSubscriptionController).getSubId(phoneId);
-        doReturn(mSubInfo).when(mSubscriptionController).getSubscriptionInfo(eq(FAKE_SUB_ID_1));
-        doReturn(carrierPackageName).when(mTelephonyManager)
-                .getCarrierServicePackageNameForLogicalSlot(eq(phoneId));
-        ((MockContentResolver) mContext.getContentResolver()).addProvider(
-                SubscriptionManager.CONTENT_URI.getAuthority(),
-                new FakeSubscriptionContentProvider());
-
-        mUpdater.updateSubscriptionByCarrierConfig(mPhone.getPhoneId(),
-                carrierPackageName, carrierConfig);
-
-        ArgumentCaptor<ContentValues> cvCaptor = ArgumentCaptor.forClass(ContentValues.class);
-        verify(mContentProvider, times(1)).update(
-                eq(SubscriptionManager.getUriForSubscriptionId(FAKE_SUB_ID_1)),
-                cvCaptor.capture(), eq(null), eq(null));
-        assertEquals(1, cvCaptor.getValue().getAsInteger(
-                SubscriptionManager.IS_OPPORTUNISTIC).intValue());
-        assertEquals("11111111-2222-3333-4444-555555555555",
-                cvCaptor.getValue().getAsString(SubscriptionManager.GROUP_UUID));
-        assertEquals(carrierPackageName,
-                cvCaptor.getValue().getAsString(SubscriptionManager.GROUP_OWNER));
-        // 4 updates: isOpportunistic, groupUuid, groupOwner, and carrier certs:
-        assertEquals(4, cvCaptor.getValue().size());
-    }
-
-    @Test
-    @SmallTest
-    public void testUpdateFromCarrierConfigOpportunisticRemoveFromGroup() throws Exception {
-        final int phoneId = mPhone.getPhoneId();
-        PersistableBundle carrierConfig = getCarrierConfigForSubInfoUpdate(
-                true, "00000000-0000-0000-0000-000000000000");
-        String carrierPackageName = "FakeCarrierPackageName";
-
-        doReturn(true).when(mSubscriptionController).canPackageManageGroup(
-                ParcelUuid.fromString("11111111-2222-3333-4444-555555555555"), carrierPackageName);
-        doReturn(FAKE_SUB_ID_1).when(mSubscriptionController).getSubId(phoneId);
-        doReturn(mSubInfo).when(mSubscriptionController).getSubscriptionInfo(eq(FAKE_SUB_ID_1));
-        doReturn(ParcelUuid.fromString("11111111-2222-3333-4444-555555555555"))
-            .when(mSubInfo).getGroupUuid();
-        doReturn(carrierPackageName).when(mTelephonyManager)
-                .getCarrierServicePackageNameForLogicalSlot(eq(phoneId));
-        ((MockContentResolver) mContext.getContentResolver()).addProvider(
-                SubscriptionManager.CONTENT_URI.getAuthority(),
-                new FakeSubscriptionContentProvider());
-
-        mUpdater.updateSubscriptionByCarrierConfig(mPhone.getPhoneId(),
-                carrierPackageName, carrierConfig);
-
-        ArgumentCaptor<ContentValues> cvCaptor = ArgumentCaptor.forClass(ContentValues.class);
-        verify(mContentProvider, times(1)).update(
-                eq(SubscriptionManager.getUriForSubscriptionId(FAKE_SUB_ID_1)),
-                cvCaptor.capture(), eq(null), eq(null));
-        assertEquals(1, cvCaptor.getValue().getAsInteger(
-                SubscriptionManager.IS_OPPORTUNISTIC).intValue());
-        assertNull(cvCaptor.getValue().getAsString(SubscriptionManager.GROUP_UUID));
-        // 3 updates: isOpportunistic, groupUuid, and carrier certs:
-        assertEquals(3, cvCaptor.getValue().size());
-    }
-
-    @Test
-    @SmallTest
-    public void testUpdateFromCarrierConfigPreferredUsageSettingDataCentric() throws Exception {
-        testUpdateFromCarrierConfigPreferredUsageSetting(
-                SubscriptionManager.USAGE_SETTING_UNKNOWN,
-                SubscriptionManager.USAGE_SETTING_DATA_CENTRIC,
-                SubscriptionManager.USAGE_SETTING_DATA_CENTRIC);
-    }
-
-    @Test
-    @SmallTest
-    public void testUpdateFromCarrierConfigPreferredUsageSettingDataCentric2() throws Exception {
-        testUpdateFromCarrierConfigPreferredUsageSetting(
-                SubscriptionManager.USAGE_SETTING_DEFAULT,
-                SubscriptionManager.USAGE_SETTING_DATA_CENTRIC,
-                SubscriptionManager.USAGE_SETTING_DATA_CENTRIC);
-    }
-
-    @Test
-    @SmallTest
-    public void testUpdateFromCarrierConfigPreferredUsageSettingDefault() throws Exception {
-        testUpdateFromCarrierConfigPreferredUsageSetting(
-                SubscriptionManager.USAGE_SETTING_DATA_CENTRIC,
-                SubscriptionManager.USAGE_SETTING_DEFAULT,
-                SubscriptionManager.USAGE_SETTING_DEFAULT);
-    }
-
-    @Test
-    @SmallTest
-    public void testUpdateFromCarrierConfigPreferredUsageSettingNoChange() throws Exception {
-        testUpdateFromCarrierConfigPreferredUsageSetting(
-                SubscriptionManager.USAGE_SETTING_DATA_CENTRIC,
-                SubscriptionManager.USAGE_SETTING_DATA_CENTRIC,
-                SubscriptionManager.USAGE_SETTING_DATA_CENTRIC);
-    }
-
-    @Test
-    @SmallTest
-    public void testUpdateFromCarrierConfigPreferredUsageSettingInvalid() throws Exception {
-        testUpdateFromCarrierConfigPreferredUsageSetting(
-                SubscriptionManager.USAGE_SETTING_DATA_CENTRIC,
-                SubscriptionManager.USAGE_SETTING_UNKNOWN,
-                SubscriptionManager.USAGE_SETTING_DATA_CENTRIC);
-    }
-
-    private PersistableBundle getCarrierConfigForSubInfoUpdateUsageSetting(
-            @SubscriptionManager.UsageSetting int usageSetting) {
-        PersistableBundle p = new PersistableBundle();
-        p.putString(CarrierConfigManager.KEY_SUBSCRIPTION_GROUP_UUID_STRING, "");
-        p.putBoolean(CarrierConfigManager.KEY_IS_OPPORTUNISTIC_SUBSCRIPTION_BOOL, false);
-        p.putInt(CarrierConfigManager.KEY_CELLULAR_USAGE_SETTING_INT, usageSetting);
-        return p;
-    }
-
-    private void testUpdateFromCarrierConfigPreferredUsageSetting(
-            int initialSetting, int requestedSetting, int expectedSetting) throws Exception {
-        final String carrierPackageName = "FakeCarrierPackageName";
-        final int phoneId = mPhone.getPhoneId();
-
-        // Install fixtures, ensure the test will hit the right code path
-        doReturn(carrierPackageName).when(mTelephonyManager)
-                .getCarrierServicePackageNameForLogicalSlot(eq(phoneId));
-        ((MockContentResolver) mContext.getContentResolver()).addProvider(
-                SubscriptionManager.CONTENT_URI.getAuthority(),
-                new FakeSubscriptionContentProvider());
-
-        // Setup overlay
-        setupUsageSettingResources();
-
-        // Setup subscription
-        doReturn(FAKE_SUB_ID_1).when(mSubscriptionController).getSubId(phoneId);
-        doReturn(mSubInfo).when(mSubscriptionController).getSubscriptionInfo(eq(FAKE_SUB_ID_1));
-        doReturn(null).when(mSubInfo).getGroupUuid();
-        doReturn(false).when(mSubInfo).isOpportunistic();
-        doReturn(initialSetting).when(mSubInfo).getUsageSetting();
-
-        // Get a config bundle for that prefers data centric
-        PersistableBundle carrierConfig = getCarrierConfigForSubInfoUpdateUsageSetting(
-                requestedSetting);
-
-        mUpdater.updateSubscriptionByCarrierConfig(mPhone.getPhoneId(),
-                carrierPackageName, carrierConfig);
-
-        ArgumentCaptor<ContentValues> cvCaptor = ArgumentCaptor.forClass(ContentValues.class);
-        verify(mContentProvider, times(1)).update(
-                eq(SubscriptionManager.getUriForSubscriptionId(FAKE_SUB_ID_1)),
-                cvCaptor.capture(), eq(null), eq(null));
-
-        if (initialSetting != expectedSetting) {
-            assertEquals(expectedSetting,
-                    (int) cvCaptor.getValue().getAsInteger(SubscriptionManager.USAGE_SETTING));
-        } else {
-            // If the content value was not set, the captor value will be null
-            assertNull(cvCaptor.getValue().getAsInteger(SubscriptionManager.USAGE_SETTING));
-        }
-    }
-
-    @Test
-    @SmallTest
-    public void testUpdateFromCarrierConfigCarrierCertificates() {
-        String[] certs = new String[2];
-        certs[0] = "d1f1";
-        certs[1] = "b5d6";
-
-        UiccAccessRule[] carrierConfigAccessRules = new UiccAccessRule[certs.length];
-        for (int i = 0; i < certs.length; i++) {
-            carrierConfigAccessRules[i] = new UiccAccessRule(
-                IccUtils.hexStringToBytes(certs[i]), null, 0);
-        }
-
-        final int phoneId = mPhone.getPhoneId();
-        PersistableBundle carrierConfig = new PersistableBundle();
-        carrierConfig.putStringArray(
-                CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY, certs);
-
-        String carrierPackageName = "FakeCarrierPackageName";
-
-        doReturn(FAKE_SUB_ID_1).when(mSubscriptionController).getSubId(phoneId);
-        doReturn(mSubInfo).when(mSubscriptionController).getSubscriptionInfo(eq(FAKE_SUB_ID_1));
-        doReturn(false).when(mSubInfo).isOpportunistic();
-        doReturn(carrierPackageName).when(mTelephonyManager)
-                .getCarrierServicePackageNameForLogicalSlot(eq(phoneId));
-        ((MockContentResolver) mContext.getContentResolver()).addProvider(
-                SubscriptionManager.CONTENT_URI.getAuthority(),
-                new FakeSubscriptionContentProvider());
-
-        mUpdater.updateSubscriptionByCarrierConfig(mPhone.getPhoneId(),
-                carrierPackageName, carrierConfig);
-
-        ArgumentCaptor<ContentValues> cvCaptor = ArgumentCaptor.forClass(ContentValues.class);
-        verify(mContentProvider, times(1)).update(
-                eq(SubscriptionManager.getUriForSubscriptionId(FAKE_SUB_ID_1)),
-                cvCaptor.capture(), eq(null), eq(null));
-        assertEquals(carrierConfigAccessRules, UiccAccessRule.decodeRules(cvCaptor.getValue()
-                .getAsByteArray(SubscriptionManager.ACCESS_RULES_FROM_CARRIER_CONFIGS)));
-        assertEquals(1, cvCaptor.getValue().size());
-        verify(mSubscriptionController, times(1)).refreshCachedActiveSubscriptionInfoList();
-        verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged();
-    }
-
-    @Test
-    @SmallTest
-    public void testSimReady() throws Exception {
-        replaceInstance(SubscriptionInfoUpdater.class, "sIccId", null,new String[]{""});
-        doReturn(mUiccPort).when(mUiccController).getUiccPort(anyInt());
-        doReturn(FAKE_ICCID_1).when(mUiccPort).getIccId();
-
-        mUpdater.updateInternalIccState(
-            IccCardConstants.INTENT_VALUE_ICC_READY, "TESTING", FAKE_SUB_ID_1);
-        processAllMessages();
-
-        verify(mSubscriptionController).clearSubInfoRecord(eq(FAKE_SUB_ID_1));
-        verify(mSubscriptionManager, times(1)).addSubscriptionInfoRecord(
-                eq(FAKE_ICCID_1), eq(FAKE_SUB_ID_1));
-        assertTrue(mUpdater.isSubInfoInitialized());
-        verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged();
-    }
-
-    @Test
-    @SmallTest
-    public void testSimReadyAndLoaded() throws Exception {
-        replaceInstance(SubscriptionInfoUpdater.class, "sIccId", null,new String[]{""});
-
-        doReturn(mUiccPort).when(mUiccController).getUiccPort(anyInt());
-        doReturn(null).when(mUiccPort).getIccId();
-
-        mUpdater.updateInternalIccState(
-            IccCardConstants.INTENT_VALUE_ICC_READY, "TESTING", FAKE_SUB_ID_1);
-        processAllMessages();
-
-        verify(mSubscriptionManager, times(0)).addSubscriptionInfoRecord(
-                eq(FAKE_ICCID_1), eq(FAKE_SUB_ID_1));
-
-        loadSim();
-
-        SubscriptionManager mSubscriptionManager = SubscriptionManager.from(mContext);
-        verify(mSubscriptionManager, times(1)).addSubscriptionInfoRecord(
-                eq(FAKE_ICCID_1), eq(FAKE_SUB_ID_1));
-        verify(mSubscriptionController, times(1)).notifySubscriptionInfoChanged();
-    }
-
-    private void setupUsageSettingResources() {
-        // The most common case, request a voice-centric->data-centric change
-        mContextFixture.putIntResource(
-                com.android.internal.R.integer.config_default_cellular_usage_setting,
-                SubscriptionManager.USAGE_SETTING_VOICE_CENTRIC);
-        mContextFixture.putIntArrayResource(
-                com.android.internal.R.array.config_supported_cellular_usage_settings,
-                new int[]{
-                        SubscriptionManager.USAGE_SETTING_VOICE_CENTRIC,
-                        SubscriptionManager.USAGE_SETTING_DATA_CENTRIC});
-    }
-
-    @Test
-    @SmallTest
-    public void testCalculateUsageSetting() throws Exception {
-        setupUsageSettingResources();
-        assertEquals(SubscriptionManager.USAGE_SETTING_DATA_CENTRIC,
-                mUpdater.calculateUsageSetting(
-                    SubscriptionManager.USAGE_SETTING_VOICE_CENTRIC,
-                    SubscriptionManager.USAGE_SETTING_DATA_CENTRIC));
-
-        // Test that a voice-centric-only device only allows voice-centric configuration
-        mContextFixture.putIntArrayResource(
-                com.android.internal.R.array.config_supported_cellular_usage_settings,
-                new int[]{SubscriptionManager.USAGE_SETTING_VOICE_CENTRIC});
-
-        assertEquals(SubscriptionManager.USAGE_SETTING_VOICE_CENTRIC,
-                mUpdater.calculateUsageSetting(
-                    SubscriptionManager.USAGE_SETTING_VOICE_CENTRIC,
-                    SubscriptionManager.USAGE_SETTING_DATA_CENTRIC));
-    }
-}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyAdminReceiverTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyAdminReceiverTest.java
new file mode 100644
index 0000000..118daa5
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyAdminReceiverTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+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.Intent;
+import android.os.UserManager;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class TelephonyAdminReceiverTest extends TelephonyTest {
+
+    private TelephonyAdminReceiver mTelephonyAdminReceiver;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        mTelephonyAdminReceiver = new TelephonyAdminReceiver(mContext, mPhone);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    public void test_nullUserManager() {
+        mUserManager = null;
+        TelephonyAdminReceiver telephonyAdminReceiver = new TelephonyAdminReceiver(mContext,
+                mPhone);
+        assertFalse(telephonyAdminReceiver.isCellular2gDisabled());
+    }
+
+    @Test
+    public void test_nullIntent_noUpdate() {
+        assertFalse(mTelephonyAdminReceiver.isCellular2gDisabled());
+
+        mContext.sendBroadcast(new Intent(UserManager.ACTION_USER_RESTRICTIONS_CHANGED));
+
+        verify(mPhone, never()).sendSubscriptionSettings(anyBoolean());
+        assertFalse(mTelephonyAdminReceiver.isCellular2gDisabled());
+    }
+
+    @Test
+    public void test_userRestrictionsNotChanged_noUpdate() {
+        assertFalse(mTelephonyAdminReceiver.isCellular2gDisabled());
+        when(mUserManager.hasUserRestriction(UserManager.DISALLOW_CELLULAR_2G)).thenReturn(false);
+
+        mContext.sendBroadcast(new Intent(UserManager.ACTION_USER_RESTRICTIONS_CHANGED));
+
+        verify(mPhone, never()).sendSubscriptionSettings(anyBoolean());
+        assertFalse(mTelephonyAdminReceiver.isCellular2gDisabled());
+    }
+
+    @Test
+    public void test_userRestrictionToggled_shouldUpdate() {
+        assertFalse(mTelephonyAdminReceiver.isCellular2gDisabled());
+        when(mUserManager.hasUserRestriction(UserManager.DISALLOW_CELLULAR_2G)).thenReturn(
+                true).thenReturn(false);
+
+        mContext.sendBroadcast(new Intent(UserManager.ACTION_USER_RESTRICTIONS_CHANGED));
+        assertTrue(mTelephonyAdminReceiver.isCellular2gDisabled());
+
+        mContext.sendBroadcast(new Intent(UserManager.ACTION_USER_RESTRICTIONS_CHANGED));
+        assertFalse(mTelephonyAdminReceiver.isCellular2gDisabled());
+        verify(mPhone, times(2)).sendSubscriptionSettings(false);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java
index 0e6e2f7..a053c56 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -37,6 +38,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.permission.LegacyPermissionManager;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
@@ -530,6 +532,14 @@
         }
     }
 
+    @Test
+    public void testCheckSubscriptionAssociatedWithUser_emergencyNumber() {
+        doReturn(true).when(mTelephonyManagerMock).isEmergencyNumber(anyString());
+
+        assertTrue(TelephonyPermissions.checkSubscriptionAssociatedWithUser(mMockContext, SUB_ID,
+                UserHandle.SYSTEM, "911"));
+    }
+
     // Put mMockTelephony into service cache so that TELEPHONY_SUPPLIER will get it.
     private void setTelephonyMockAsService() throws Exception {
         when(mMockTelephonyBinder.queryLocalInterface(anyString())).thenReturn(mMockTelephony);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
index 91dd89d..35a3186 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
@@ -25,7 +25,9 @@
 import static android.telephony.TelephonyManager.RADIO_POWER_UNAVAILABLE;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -35,9 +37,11 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.Manifest;
 import android.content.Intent;
 import android.content.pm.UserInfo;
 import android.net.LinkProperties;
+import android.os.Build;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
@@ -108,6 +112,8 @@
     private CellLocation mCellLocation;
     private List<CellInfo> mCellInfo;
     private BarringInfo mBarringInfo = null;
+    private CellIdentity mCellIdentityForRegiFail;
+    private int mRegistrationFailReason;
 
     // All events contribute to TelephonyRegistry#isPhoneStatePermissionRequired
     private static final Set<Integer> READ_PHONE_STATE_EVENTS;
@@ -151,6 +157,8 @@
                 TelephonyCallback.EVENT_VOICE_ACTIVATION_STATE_CHANGED);
         READ_PRIVILEGED_PHONE_STATE_EVENTS.add(
                 TelephonyCallback.EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED);
+        READ_PRIVILEGED_PHONE_STATE_EVENTS.add(
+                TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED);
     }
 
     // All events contribute to TelephonyRegistry#isActiveEmergencySessionPermissionRequired
@@ -176,7 +184,8 @@
             TelephonyCallback.CellLocationListener,
             TelephonyCallback.ServiceStateListener,
             TelephonyCallback.CellInfoListener,
-            TelephonyCallback.BarringInfoListener {
+            TelephonyCallback.BarringInfoListener,
+            TelephonyCallback.RegistrationFailedListener {
         // 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);
@@ -250,6 +259,15 @@
             invocationCount.incrementAndGet();
             mBarringInfo = barringInfo;
         }
+
+        public void onRegistrationFailed(@android.annotation.NonNull CellIdentity cellIdentity,
+                @android.annotation.NonNull String chosenPlmn,
+                @NetworkRegistrationInfo.Domain int domain,
+                int causeCode, int additionalCauseCode) {
+            invocationCount.incrementAndGet();
+            mCellIdentityForRegiFail = cellIdentity;
+            mRegistrationFailReason = causeCode;
+        }
     }
 
     private void addTelephonyRegistryService() {
@@ -897,24 +915,48 @@
     }
 
     @Test
-    public void testBarringInfoChanged() {
+    public void testBarringInfoChangedWithLocationFinePermission() throws Exception {
+        checkBarringInfoWithLocationPermission(Manifest.permission.ACCESS_FINE_LOCATION);
+    }
+
+    @Test
+    public void testBarringInfoChangedLocationCoarsePermission() throws Exception {
+        checkBarringInfoWithLocationPermission(Manifest.permission.ACCESS_COARSE_LOCATION);
+    }
+
+    @Test
+    public void testBarringInfoChangedWithoutLocationPermission() throws Exception {
+        checkBarringInfoWithLocationPermission(null);
+    }
+
+    private void checkBarringInfoWithLocationPermission(String permission) throws Exception {
         // Return a slotIndex / phoneId of 0 for all sub ids given.
         doReturn(mMockSubInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(anyInt());
         doReturn(0/*slotIndex*/).when(mMockSubInfo).getSimSlotIndex();
         doReturn(true).when(mLocationManager).isLocationEnabledForUser(any(UserHandle.class));
 
+        mApplicationInfo.targetSdkVersion = Build.VERSION_CODES.TIRAMISU;
+        doReturn(mApplicationInfo).when(mPackageManager).getApplicationInfo(anyString(), anyInt());
+        mContextFixture.addCallingOrSelfPermission("");
+        mContextFixture.addCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE);
+        mContextFixture.addCallingOrSelfPermission(
+                android.Manifest.permission.READ_PRECISE_PHONE_STATE);
+        if (permission != null) {
+            mContextFixture.addCallingOrSelfPermission(permission);
+        }
+
         final int subId = 1;
         int[] events = {TelephonyCallback.EVENT_BARRING_INFO_CHANGED};
         SparseArray<BarringInfo.BarringServiceInfo> bsi = new SparseArray(1);
-        bsi.set(BarringInfo.BARRING_SERVICE_TYPE_MO_DATA,
+        bsi.set(BarringInfo.BARRING_SERVICE_TYPE_MMTEL_VOICE,
                 new BarringInfo.BarringServiceInfo(
                         BarringInfo.BarringServiceInfo.BARRING_TYPE_CONDITIONAL,
                         false /*isConditionallyBarred*/,
                         30 /*conditionalBarringFactor*/,
                         10 /*conditionalBarringTimeSeconds*/));
-        BarringInfo info = new BarringInfo(new CellIdentityLte(), bsi);
-
-        // Registering for info causes Barring Info to be sent to caller
+        BarringInfo info = new BarringInfo(
+                new CellIdentityLte(777, 333, 12345, 222, 13579), bsi);
+        // 1. Register listener which requires location access.
         mTelephonyRegistry.listenWithEventList(false, false, subId, mContext.getOpPackageName(),
                 mContext.getAttributionTag(), mTelephonyCallback.callback, events, true);
         processAllMessages();
@@ -925,12 +967,115 @@
         mTelephonyRegistry.notifyBarringInfoChanged(0, subId, info);
         processAllMessages();
         assertEquals(2, mTelephonyCallback.invocationCount.get());
-        assertEquals(mBarringInfo, info);
+        assertEquals(mBarringInfo
+                        .getBarringServiceInfo(BarringInfo.BARRING_SERVICE_TYPE_MMTEL_VOICE),
+                info.getBarringServiceInfo(BarringInfo.BARRING_SERVICE_TYPE_MMTEL_VOICE));
+        String log = mBarringInfo.toString();
+        assertTrue(log.contains("777"));
+        assertTrue(log.contains("333"));
+        if (permission != null && permission.equals(Manifest.permission.ACCESS_FINE_LOCATION)) {
+            assertTrue(log.contains("12345"));
+            assertTrue(log.contains("222"));
+            assertTrue(log.contains("13579"));
+        } else {
+            assertFalse(log.contains("12345"));
+            assertFalse(log.contains("222"));
+            assertFalse(log.contains("13579"));
+        }
 
         // Duplicate BarringInfo notifications do not trigger callback
         mTelephonyRegistry.notifyBarringInfoChanged(0, subId, info);
         processAllMessages();
         assertEquals(2, mTelephonyCallback.invocationCount.get());
+
+        mTelephonyRegistry.listenWithEventList(true, true, subId, mContext.getOpPackageName(),
+                mContext.getAttributionTag(), mTelephonyCallback.callback, new int[0], true);
+        // 2. Register listener renounces location access.
+        mTelephonyRegistry.listenWithEventList(true, true, subId, mContext.getOpPackageName(),
+                mContext.getAttributionTag(), mTelephonyCallback.callback, events, true);
+        processAllMessages();
+        // check receiving barring info without location info.
+        assertEquals(3, mTelephonyCallback.invocationCount.get());
+        assertNotNull(mBarringInfo);
+        assertEquals(mBarringInfo
+                        .getBarringServiceInfo(BarringInfo.BARRING_SERVICE_TYPE_MMTEL_VOICE),
+                info.getBarringServiceInfo(BarringInfo.BARRING_SERVICE_TYPE_MMTEL_VOICE));
+        log = mBarringInfo.toString();
+        assertTrue(log.contains("777"));
+        assertTrue(log.contains("333"));
+        assertFalse(log.contains("12345"));
+        assertFalse(log.contains("222"));
+        assertFalse(log.contains("13579"));
+    }
+
+    @Test
+    public void testRegistrationFailedEventWithLocationFinePermission() throws Exception {
+        checkRegistrationFailedEventWithLocationPermission(
+                Manifest.permission.ACCESS_FINE_LOCATION);
+    }
+    @Test
+    public void testRegistrationFailedEventWithLocationCoarsePermission() throws Exception {
+        checkRegistrationFailedEventWithLocationPermission(
+                Manifest.permission.ACCESS_COARSE_LOCATION);
+    }
+
+    @Test
+    public void testRegistrationFailedEventWithoutLocationPermission() throws Exception {
+        checkRegistrationFailedEventWithLocationPermission(null);
+    }
+
+    private void checkRegistrationFailedEventWithLocationPermission(String permission)
+            throws Exception {
+        // Return a slotIndex / phoneId of 0 for all sub ids given.
+        doReturn(mMockSubInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(anyInt());
+        doReturn(0/*slotIndex*/).when(mMockSubInfo).getSimSlotIndex();
+        doReturn(true).when(mLocationManager).isLocationEnabledForUser(any(UserHandle.class));
+
+        mApplicationInfo.targetSdkVersion = Build.VERSION_CODES.TIRAMISU;
+        doReturn(mApplicationInfo).when(mPackageManager).getApplicationInfo(anyString(), anyInt());
+        mContextFixture.addCallingOrSelfPermission("");
+        mContextFixture.addCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE);
+        mContextFixture.addCallingOrSelfPermission(
+                android.Manifest.permission.READ_PRECISE_PHONE_STATE);
+        if (permission != null) {
+            mContextFixture.addCallingOrSelfPermission(permission);
+        }
+
+        final int subId = 1;
+        int[] events = {TelephonyCallback.EVENT_REGISTRATION_FAILURE};
+        CellIdentity cellIdentity =
+                new CellIdentityLte(777, 333, 12345, 227, 13579);
+
+        // 1. Register listener which requires location access.
+        mTelephonyRegistry.listenWithEventList(false, false, subId, mContext.getOpPackageName(),
+                mContext.getAttributionTag(), mTelephonyCallback.callback, events, true);
+        processAllMessages();
+        int invocationCount = mTelephonyCallback.invocationCount.get();
+        // Updating the RegistrationFailed info to be updated
+        mTelephonyRegistry.notifyRegistrationFailed(
+                0, subId, cellIdentity, "88888", 1, 333, 22);
+        processAllMessages();
+        assertEquals(invocationCount + 1, mTelephonyCallback.invocationCount.get());
+        if (permission != null && permission.equals(Manifest.permission.ACCESS_FINE_LOCATION)) {
+            assertEquals(cellIdentity, mCellIdentityForRegiFail);
+        } else {
+            assertEquals(cellIdentity.sanitizeLocationInfo(), mCellIdentityForRegiFail);
+        }
+        assertEquals(333, mRegistrationFailReason);
+        mTelephonyRegistry.listenWithEventList(true, true, subId, mContext.getOpPackageName(),
+                mContext.getAttributionTag(), mTelephonyCallback.callback, new int[0], true);
+
+        // 2. Register listener which renounces location access.
+        mTelephonyRegistry.listenWithEventList(true, true, subId, mContext.getOpPackageName(),
+                mContext.getAttributionTag(), mTelephonyCallback.callback, events, true);
+        invocationCount = mTelephonyCallback.invocationCount.get();
+        // Updating the RegistrationFailed info to be updated
+        mTelephonyRegistry.notifyRegistrationFailed(
+                0, subId, cellIdentity, "88888", 1, 555, 22);
+        processAllMessages();
+        assertEquals(invocationCount + 1, mTelephonyCallback.invocationCount.get());
+        assertEquals(cellIdentity.sanitizeLocationInfo(), mCellIdentityForRegiFail);
+        assertEquals(555, mRegistrationFailReason);
     }
 
     /**
@@ -1170,12 +1315,9 @@
         final int subId = 1;
 
         // Return a slotIndex / phoneId of 0 for subId 1.
-        doReturn(subId).when(mSubscriptionController).getSubId(phoneId);
+        doReturn(subId).when(mSubscriptionManagerService).getSubId(phoneId);
         doReturn(mMockSubInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(subId);
         doReturn(phoneId).when(mMockSubInfo).getSimSlotIndex();
-        mServiceManagerMockedServices.put("isub", mSubscriptionController);
-        doReturn(mSubscriptionController).when(mSubscriptionController)
-                .queryLocalInterface(anyString());
 
         UserInfo userInfo = new UserInfo(UserHandle.myUserId(), "" /* name */, 0 /* flags */);
         doReturn(userInfo.id).when(mIActivityManager).getCurrentUserId();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
index 6bed1b6..b044814 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
@@ -16,7 +16,8 @@
 
 package com.android.internal.telephony;
 
-import static org.junit.Assert.assertNotNull;
+import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_UNKNOWN;
+
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -107,13 +108,17 @@
 import com.android.internal.telephony.data.PhoneSwitcher;
 import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
+import com.android.internal.telephony.imsphone.ImsNrSaModeHandler;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
+import com.android.internal.telephony.metrics.DeviceStateHelper;
 import com.android.internal.telephony.metrics.ImsStats;
 import com.android.internal.telephony.metrics.MetricsCollector;
 import com.android.internal.telephony.metrics.PersistAtomsStorage;
+import com.android.internal.telephony.metrics.ServiceStateStats;
 import com.android.internal.telephony.metrics.SmsStats;
 import com.android.internal.telephony.metrics.VoiceCallSessionStats;
+import com.android.internal.telephony.satellite.SatelliteController;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.test.SimulatedCommands;
 import com.android.internal.telephony.test.SimulatedCommandsVerifier;
@@ -203,7 +208,6 @@
     protected GsmCdmaCall mGsmCdmaCall;
     protected ImsCall mImsCall;
     protected ImsEcbm mImsEcbm;
-    protected SubscriptionController mSubscriptionController;
     protected SubscriptionManagerService mSubscriptionManagerService;
     protected ServiceState mServiceState;
     protected IPackageManager.Stub mMockPackageManager;
@@ -232,6 +236,7 @@
     protected CarrierSignalAgent mCarrierSignalAgent;
     protected CarrierActionAgent mCarrierActionAgent;
     protected ImsExternalCallTracker mImsExternalCallTracker;
+    protected ImsNrSaModeHandler mImsNrSaModeHandler;
     protected AppSmsManager mAppSmsManager;
     protected IccSmsInterfaceManager mIccSmsInterfaceManager;
     protected SmsDispatchersController mSmsDispatchersController;
@@ -240,7 +245,6 @@
     protected IntentBroadcaster mIntentBroadcaster;
     protected NitzStateMachine mNitzStateMachine;
     protected RadioConfig mMockRadioConfig;
-    protected SubscriptionInfoUpdater mSubInfoRecordUpdater;
     protected LocaleTracker mLocaleTracker;
     protected RestrictedState mRestrictedState;
     protected PhoneConfigurationManager mPhoneConfigurationManager;
@@ -266,6 +270,9 @@
     protected CellLocation mCellLocation;
     protected DataServiceManager mMockedWwanDataServiceManager;
     protected DataServiceManager mMockedWlanDataServiceManager;
+    protected ServiceStateStats mServiceStateStats;
+    protected SatelliteController mSatelliteController;
+    protected DeviceStateHelper mDeviceStateHelper;
 
     // Initialized classes
     protected ActivityManager mActivityManager;
@@ -437,7 +444,6 @@
         mGsmCdmaCall = Mockito.mock(GsmCdmaCall.class);
         mImsCall = Mockito.mock(ImsCall.class);
         mImsEcbm = Mockito.mock(ImsEcbm.class);
-        mSubscriptionController = Mockito.mock(SubscriptionController.class);
         mSubscriptionManagerService = Mockito.mock(SubscriptionManagerService.class);
         mServiceState = Mockito.mock(ServiceState.class);
         mMockPackageManager = Mockito.mock(IPackageManager.Stub.class);
@@ -466,6 +472,7 @@
         mCarrierSignalAgent = Mockito.mock(CarrierSignalAgent.class);
         mCarrierActionAgent = Mockito.mock(CarrierActionAgent.class);
         mImsExternalCallTracker = Mockito.mock(ImsExternalCallTracker.class);
+        mImsNrSaModeHandler = Mockito.mock(ImsNrSaModeHandler.class);
         mAppSmsManager = Mockito.mock(AppSmsManager.class);
         mIccSmsInterfaceManager = Mockito.mock(IccSmsInterfaceManager.class);
         mSmsDispatchersController = Mockito.mock(SmsDispatchersController.class);
@@ -474,7 +481,6 @@
         mIntentBroadcaster = Mockito.mock(IntentBroadcaster.class);
         mNitzStateMachine = Mockito.mock(NitzStateMachine.class);
         mMockRadioConfig = Mockito.mock(RadioConfig.class);
-        mSubInfoRecordUpdater = Mockito.mock(SubscriptionInfoUpdater.class);
         mLocaleTracker = Mockito.mock(LocaleTracker.class);
         mRestrictedState = Mockito.mock(RestrictedState.class);
         mPhoneConfigurationManager = Mockito.mock(PhoneConfigurationManager.class);
@@ -500,6 +506,9 @@
         mCellLocation = Mockito.mock(CellLocation.class);
         mMockedWwanDataServiceManager = Mockito.mock(DataServiceManager.class);
         mMockedWlanDataServiceManager = Mockito.mock(DataServiceManager.class);
+        mServiceStateStats = Mockito.mock(ServiceStateStats.class);
+        mSatelliteController = Mockito.mock(SatelliteController.class);
+        mDeviceStateHelper = Mockito.mock(DeviceStateHelper.class);
 
         TelephonyManager.disableServiceHandleCaching();
         PropertyInvalidatedCache.disableForTestMode();
@@ -525,7 +534,9 @@
 
         Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
 
-        enableSubscriptionManagerService(true);
+        mServiceManagerMockedServices.put("isub", mSubscriptionManagerService);
+        doReturn(mSubscriptionManagerService).when(mSubscriptionManagerService)
+                .queryLocalInterface(anyString());
 
         mPhone.mCi = mSimulatedCommands;
         mCT.mCi = mSimulatedCommands;
@@ -585,6 +596,8 @@
                         anyInt(), nullable(Object.class));
         doReturn(mImsExternalCallTracker).when(mTelephonyComponentFactory)
                 .makeImsExternalCallTracker(nullable(ImsPhone.class));
+        doReturn(mImsNrSaModeHandler).when(mTelephonyComponentFactory)
+                .makeImsNrSaModeHandler(nullable(ImsPhone.class));
         doReturn(mAppSmsManager).when(mTelephonyComponentFactory)
                 .makeAppSmsManager(nullable(Context.class));
         doReturn(mCarrierSignalAgent).when(mTelephonyComponentFactory)
@@ -716,7 +729,14 @@
         doReturn(mPhone).when(mInboundSmsHandler).getPhone();
         doReturn(mImsCallProfile).when(mImsCall).getCallProfile();
         doReturn(mIBinder).when(mIIntentSender).asBinder();
-        doReturn(mIIntentSender).when(mIActivityManager).getIntentSenderWithFeature(anyInt(),
+        doAnswer(invocation -> {
+            Intent[] intents = invocation.getArgument(6);
+            if (intents != null && intents.length > 0) {
+                doReturn(intents[0]).when(mIActivityManager)
+                        .getIntentForIntentSender(mIIntentSender);
+            }
+            return mIIntentSender;
+        }).when(mIActivityManager).getIntentSenderWithFeature(anyInt(),
                 nullable(String.class), nullable(String.class), nullable(IBinder.class),
                 nullable(String.class), anyInt(), nullable(Intent[].class),
                 nullable(String[].class), anyInt(), nullable(Bundle.class), anyInt());
@@ -725,6 +745,7 @@
 
         doReturn(TelephonyManager.PHONE_TYPE_GSM).when(mTelephonyManager).getPhoneType();
         doReturn(mServiceState).when(mSST).getServiceState();
+        doReturn(mServiceStateStats).when(mSST).getServiceStateStats();
         mSST.mSS = mServiceState;
         mSST.mRestrictedState = mRestrictedState;
         mServiceManagerMockedServices.put("connectivity_metrics_logger", mConnMetLoggerBinder);
@@ -734,13 +755,10 @@
         doReturn(new int[]{AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
                 AccessNetworkConstants.TRANSPORT_TYPE_WLAN})
                 .when(mAccessNetworksManager).getAvailableTransports();
-        doReturn(new int[]{AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
-                AccessNetworkConstants.TRANSPORT_TYPE_WLAN})
-                .when(mAccessNetworksManager).getAvailableTransports();
         doReturn(true).when(mDataSettingsManager).isDataEnabled();
         doReturn(mNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
                 anyInt(), anyInt());
-        doReturn(RIL.RADIO_HAL_VERSION_2_0).when(mPhone).getHalVersion();
+        doReturn(RIL.RADIO_HAL_VERSION_2_0).when(mPhone).getHalVersion(anyInt());
         doReturn(2).when(mSignalStrength).getLevel();
 
         // WiFi
@@ -815,6 +833,11 @@
         doReturn(null).when(mContext).getFileStreamPath(anyString());
         doReturn(mPersistAtomsStorage).when(mMetricsCollector).getAtomsStorage();
         doReturn(mWifiManager).when(mContext).getSystemService(eq(Context.WIFI_SERVICE));
+        doReturn(mDeviceStateHelper).when(mMetricsCollector).getDeviceStateHelper();
+        doReturn(CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_UNKNOWN)
+                .when(mDeviceStateHelper)
+                .getFoldState();
+        doReturn(null).when(mContext).getSystemService(eq(Context.DEVICE_STATE_SERVICE));
 
         //Use reflection to mock singletons
         replaceInstance(CallManager.class, "INSTANCE", null, mCallManager);
@@ -822,7 +845,6 @@
                 mTelephonyComponentFactory);
         replaceInstance(UiccController.class, "mInstance", null, mUiccController);
         replaceInstance(CdmaSubscriptionSourceManager.class, "sInstance", null, mCdmaSSM);
-        replaceInstance(SubscriptionController.class, "sInstance", null, mSubscriptionController);
         replaceInstance(SubscriptionManagerService.class, "sInstance", null,
                 mSubscriptionManagerService);
         replaceInstance(ProxyController.class, "sProxyController", null, mProxyController);
@@ -843,7 +865,6 @@
         replaceInstance(PhoneFactory.class, "sMadeDefaults", null, true);
         replaceInstance(PhoneFactory.class, "sPhone", null, mPhone);
         replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
-        replaceInstance(PhoneFactory.class, "sSubInfoRecordUpdater", null, mSubInfoRecordUpdater);
         replaceInstance(RadioConfig.class, "sRadioConfig", null, mMockRadioConfig);
         replaceInstance(PhoneConfigurationManager.class, "sInstance", null,
                 mPhoneConfigurationManager);
@@ -851,15 +872,10 @@
                 mCellularNetworkValidator);
         replaceInstance(MultiSimSettingController.class, "sInstance", null,
                 mMultiSimSettingController);
-        replaceInstance(SubscriptionInfoUpdater.class, "sIsSubInfoInitialized", null, true);
         replaceInstance(PhoneFactory.class, "sCommandsInterfaces", null,
                 new CommandsInterface[] {mSimulatedCommands});
         replaceInstance(PhoneFactory.class, "sMetricsCollector", null, mMetricsCollector);
-
-        if (!isSubscriptionManagerServiceEnabled()) {
-            assertNotNull("Failed to set up SubscriptionController singleton",
-                    SubscriptionController.getInstance());
-        }
+        replaceInstance(SatelliteController.class, "sInstance", null, mSatelliteController);
 
         setReady(false);
         // create default TestableLooper for test and add to list of monitored loopers
@@ -958,14 +974,17 @@
         private static final String PROPERTY_DEVICE_IDENTIFIER_ACCESS_RESTRICTIONS_DISABLED =
                 DeviceConfig.NAMESPACE_PRIVACY + "/"
                         + "device_identifier_access_restrictions_disabled";
+        private HashMap<String, String> mFlags = new HashMap<>();
 
         @Override
         public Bundle call(String method, String arg, Bundle extras) {
+            logd("FakeSettingsConfigProvider: call called,  method: " + method +
+                    " request: " + arg + ", args=" + extras);
+            Bundle bundle = new Bundle();
             switch (method) {
                 case Settings.CALL_METHOD_GET_CONFIG: {
                     switch (arg) {
                         case PROPERTY_DEVICE_IDENTIFIER_ACCESS_RESTRICTIONS_DISABLED: {
-                            Bundle bundle = new Bundle();
                             bundle.putString(
                                     PROPERTY_DEVICE_IDENTIFIER_ACCESS_RESTRICTIONS_DISABLED,
                                     "0");
@@ -977,6 +996,18 @@
                     }
                     break;
                 }
+                case Settings.CALL_METHOD_LIST_CONFIG:
+                    logd("LIST_config: " + mFlags);
+                    Bundle result = new Bundle();
+                    result.putSerializable(Settings.NameValueTable.VALUE, mFlags);
+                    return result;
+                case Settings.CALL_METHOD_SET_ALL_CONFIG:
+                    mFlags = (extras != null)
+                            ? (HashMap) extras.getSerializable(Settings.CALL_METHOD_FLAGS_KEY)
+                            : new HashMap<>();
+                    bundle.putInt(Settings.KEY_CONFIG_SET_ALL_RETURN,
+                            Settings.SET_ALL_RESULT_SUCCESS);
+                    return bundle;
                 default:
                     fail("Method not expected: " + method);
             }
@@ -1261,22 +1292,4 @@
             }
         }
     }
-
-    protected void enableSubscriptionManagerService(boolean enabled) throws Exception {
-        if (enabled) {
-            mServiceManagerMockedServices.put("isub", mSubscriptionManagerService);
-            doReturn(mSubscriptionManagerService).when(mIBinder)
-                    .queryLocalInterface(anyString());
-        }
-        replaceInstance(PhoneFactory.class, "sSubscriptionManagerServiceEnabled", null, enabled);
-        mContextFixture.putBooleanResource(com.android.internal.R.bool
-                .config_using_subscription_manager_service, enabled);
-        doReturn(enabled).when(mPhone).isSubscriptionManagerServiceEnabled();
-        doReturn(enabled).when(mPhone2).isSubscriptionManagerServiceEnabled();
-        doReturn(enabled).when(mImsPhone).isSubscriptionManagerServiceEnabled();
-    }
-
-    protected boolean isSubscriptionManagerServiceEnabled() {
-        return mPhone.isSubscriptionManagerServiceEnabled();
-    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/VisualVoicemailSmsFilterTest.java b/tests/telephonytests/src/com/android/internal/telephony/VisualVoicemailSmsFilterTest.java
index f09c94e..9f76337 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/VisualVoicemailSmsFilterTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/VisualVoicemailSmsFilterTest.java
@@ -96,7 +96,7 @@
         VisualVoicemailSmsFilter.setPhoneAccountHandleConverterForTest(
                 new PhoneAccountHandleConverter() {
                     @Override
-                    public PhoneAccountHandle fromSubId(int subId) {
+                    public PhoneAccountHandle fromSubId(int subId, Context context) {
                         return new PhoneAccountHandle(
                                 new ComponentName("com.android.internal.telephony",
                                         "VisualVoicemailSmsFilterTest"), "foo");
diff --git a/tests/telephonytests/src/com/android/internal/telephony/cat/CATServiceTest.java b/tests/telephonytests/src/com/android/internal/telephony/cat/CATServiceTest.java
new file mode 100644
index 0000000..f2c1870
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/cat/CATServiceTest.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2022 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.cat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.os.UserHandle;
+import android.telephony.SmsManager;
+import android.telephony.SmsMessage;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.telephony.ProxyController;
+import com.android.internal.telephony.SmsController;
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.test.SimulatedCommands;
+import com.android.internal.telephony.uicc.IccCardApplicationStatus;
+import com.android.internal.telephony.uicc.IccCardStatus;
+import com.android.internal.telephony.uicc.IccFileHandler;
+import com.android.internal.telephony.uicc.IccIoResult;
+import com.android.internal.telephony.uicc.IccUtils;
+import com.android.internal.telephony.uicc.UiccCard;
+import com.android.internal.telephony.uicc.UiccProfile;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class CATServiceTest extends TelephonyTest {
+
+    private static final String SMS_SENT_ACTION =
+            "com.android.internal.telephony.cat.SMS_SENT_ACTION";
+    private static final String SMS_DELIVERY_ACTION =
+            "com.android.internal.telephony.cat.SMS_DELIVERY_ACTION";
+    //Mocked Classes
+    @Mock
+    private RilMessageDecoder mRilMessageDecoder;
+    private IccFileHandler mIccFileHandler;
+    private SmsController mSmsController;
+    private CommandDetails mCommandDetails;
+    private CatService mCatService;
+    private IccCardStatus mIccCardStatus;
+    private IccIoResult mIccIoResult;
+    private String mData =
+            "D059810301130082028183051353656E64696E672072657175657374202E2E2E0607911989548056780B"
+                    + "3051FF05812143F500F6082502700000201115001500BFFF01BA23C2169EA9B02D7A7FBAA0"
+                    + "DAABFEE8B8DE9DA06DCD234E";
+    private byte[] mRawdata = IccUtils.hexStringToBytes(mData);
+    private List<ComprehensionTlv> mCtlvs;
+
+    /**
+     * Terminal Response with result code in last 3 bytes = length + SMS_RP_ERROR(0x35)
+     * + ErrorCode(= 41)
+     */
+    private String mTerminalResponseForSmsRpError = "81030113000202828183023529";
+
+    /**
+     * Terminal Response with result code in last 3 bytes = length + NETWORK_UNABLE_TO_PROCESS(0x21)
+     * + ErrorCode(= 41 with 8th bit set to 1)
+     */
+    private String mTerminalResponseForNetworkUnableToProcess = "810301130002028281830221A9";
+
+    /**
+     * Terminal Response with result code in last 2 bytes = length
+     * + TERMINAL_UNABLE_TO_PROCESS(0x20)
+     */
+    private String mTerminalResponseForTerminalUnableToProcess = "810301130002028281830120";
+
+    //Terminal Response with result code(0x00)for delivery success in last 2 bytes
+    private String mTerminalResponseForDeliverySuccess = "810301130002028281830100";
+
+    public CATServiceTest() {
+        super();
+    }
+
+    private IccCardApplicationStatus composeUiccApplicationStatus(
+            IccCardApplicationStatus.AppType appType,
+            IccCardApplicationStatus.AppState appState, String aid) {
+        IccCardApplicationStatus mIccCardAppStatus = new IccCardApplicationStatus();
+        mIccCardAppStatus.aid = aid;
+        mIccCardAppStatus.app_type = appType;
+        mIccCardAppStatus.aid = aid;
+        mIccCardAppStatus.app_type = appType;
+        mIccCardAppStatus.app_state = appState;
+        mIccCardAppStatus.pin1 = mIccCardAppStatus.pin2 =
+                IccCardStatus.PinState.PINSTATE_ENABLED_VERIFIED;
+        return mIccCardAppStatus;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        mRilMessageDecoder = mock(RilMessageDecoder.class);
+        mIccFileHandler = mock(IccFileHandler.class);
+        mSmsController = mock(SmsController.class);
+        mIccCardStatus = mock(IccCardStatus.class);
+        mProxyController = mock(ProxyController.class);
+        mUiccCard = mock(UiccCard.class);
+        IccCardApplicationStatus umtsApp = composeUiccApplicationStatus(
+                IccCardApplicationStatus.AppType.APPTYPE_USIM,
+                IccCardApplicationStatus.AppState.APPSTATE_UNKNOWN, "0xA2");
+        mIccCardStatus.mApplications = new IccCardApplicationStatus[]{umtsApp};
+        mIccCardStatus.mCdmaSubscriptionAppIndex =
+                mIccCardStatus.mImsSubscriptionAppIndex =
+                        mIccCardStatus.mGsmUmtsSubscriptionAppIndex = -1;
+        mIccIoResult = new IccIoResult(0x90, 0x00, IccUtils.hexStringToBytes("FF40"));
+        mSimulatedCommands = mock(SimulatedCommands.class);
+        mSimulatedCommands.setIccIoResultForApduLogicalChannel(mIccIoResult);
+        mUiccProfile = new UiccProfile(mContext, mSimulatedCommands, mIccCardStatus,
+                0 /* phoneId */, mUiccCard, new Object());
+        processAllMessages();
+        logd("Created UiccProfile");
+        processAllMessages();
+        mCatService = CatService.getInstance(mSimulatedCommands, mContext,
+                mUiccProfile, mUiccController.getSlotIdFromPhoneId(0));
+        logd("Created CATService");
+        createCommandDetails();
+        createComprehensionTlvList();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mCatService.dispose();
+        mUiccProfile = null;
+        mCatService = null;
+        mCtlvs = null;
+        mProxyController = null;
+        mRilMessageDecoder = null;
+        mCommandDetails = null;
+        mContext = null;
+        mSimulatedCommands = null;
+        mIccCardStatus = null;
+        mIccCard = null;
+        mIccFileHandler = null;
+        mIccIoResult = null;
+        mSmsController = null;
+        super.tearDown();
+    }
+
+    private void createCommandDetails() {
+        mCommandDetails = mock(CommandDetails.class);
+        mCommandDetails.compRequired = true;
+        mCommandDetails.commandNumber = 1;
+        mCommandDetails.typeOfCommand = 19;
+        mCommandDetails.commandQualifier = 0;
+    }
+
+    private void createComprehensionTlvList() {
+        ComprehensionTlv ctlv1 = new ComprehensionTlv(1, false, 3, mRawdata, 4);
+        ComprehensionTlv ctlv2 = new ComprehensionTlv(2, false, 2, mRawdata, 9);
+        ComprehensionTlv ctlv3 = new ComprehensionTlv(5, false, 19, mRawdata, 13);
+        ComprehensionTlv ctlv4 = new ComprehensionTlv(6, false, 7, mRawdata, 34);
+        ComprehensionTlv ctlv5 = new ComprehensionTlv(11, false, 48, mRawdata, 43);
+        mCtlvs = new ArrayList<>();
+        mCtlvs.add(ctlv1);
+        mCtlvs.add(ctlv2);
+        mCtlvs.add(ctlv3);
+        mCtlvs.add(ctlv4);
+        mCtlvs.add(ctlv5);
+    }
+
+    @Test
+    public void testSendSmsCommandParams() throws Exception {
+        ComprehensionTlv ctlv = new ComprehensionTlv(11, false, 48, mRawdata, 43);
+        SmsMessage smsMessage = ValueParser.retrieveTpduAsSmsMessage(ctlv);
+        assertNotNull(smsMessage);
+        assertEquals("12345", smsMessage.getRecipientAddress());
+    }
+
+    @Test
+    public void testSendSTKSmsViaCatService() {
+        CommandParams cmdPrms = new CommandParams(mCommandDetails);
+        when(mProxyController.getSmsController()).thenReturn(mSmsController);
+        mCatService.sendStkSms("test", "12345", 1, cmdPrms, mProxyController);
+        verify(mSmsController, Mockito.times(1)).sendTextForSubscriber(anyInt(),
+                anyString(), nullable(String.class), anyString(), nullable(String.class),
+                anyString(), Mockito.anyObject(), any(), eq(false), anyLong(), eq(true), eq(true));
+    }
+
+    @Test
+    public void testprocessSMSEventNotify() throws Exception {
+        CommandParamsFactory cmdPF = CommandParamsFactory.getInstance(mRilMessageDecoder,
+                mIccFileHandler, mContext);
+        assertEquals(false, cmdPF.processSMSEventNotify(mCommandDetails, mCtlvs));
+    }
+
+    @Test
+    public void testSkipFdnCheckforSTKSmsViaCatService() {
+        CommandParams cmdPrms = new CommandParams(mCommandDetails);
+        when(mProxyController.getSmsController()).thenReturn(mSmsController);
+        mCatService.sendStkSms("test", "12345", 1, cmdPrms, mProxyController);
+        verify(mSmsController, Mockito.times(0)).isNumberBlockedByFDN(1, "12345",
+                "com.android.internal.telephony");
+    }
+
+    //Create and assign a PendingResult object in BroadcastReceiver with which resultCode is updated
+    private void setBroadcastReceiverPendingResult(BroadcastReceiver receiver, int resultCode) {
+        BroadcastReceiver.PendingResult pendingResult =
+                new BroadcastReceiver.PendingResult(resultCode,
+                        "resultData",
+                        /* resultExtras= */ null,
+                        BroadcastReceiver.PendingResult.TYPE_UNREGISTERED,
+                        /* ordered= */ true,
+                        /* sticky= */ false,
+                        /* token= */ null,
+                        UserHandle.myUserId(),
+                        /* flags= */ 0);
+        receiver.setPendingResult(pendingResult);
+    }
+
+    @Test
+    public void testSendTerminalResponseForSendSuccess() {
+        setBroadcastReceiverPendingResult(mCatService.mSmsBroadcastReceiver, Activity.RESULT_OK);
+        Intent intent = new Intent(SMS_SENT_ACTION).putExtra("cmdDetails", mCommandDetails);
+        intent.putExtra("ims", true);
+        mContext.sendOrderedBroadcast(intent, null, mCatService.mSmsBroadcastReceiver, null,
+                Activity.RESULT_OK, null, null);
+        processAllMessages();
+        verify(mSimulatedCommands, never()).sendTerminalResponse(
+                any(), any());
+    }
+
+    @Test
+    public void testSendTerminalResponseForSendSmsRpError() {
+        setBroadcastReceiverPendingResult(mCatService.mSmsBroadcastReceiver,
+                SmsManager.RESULT_ERROR_GENERIC_FAILURE);
+        Intent intent = new Intent(SMS_SENT_ACTION).putExtra("cmdDetails", mCommandDetails);
+        intent.putExtra("ims", true);
+        intent.putExtra("errorCode", 41);
+        mContext.sendOrderedBroadcast(intent, null, mCatService.mSmsBroadcastReceiver, null,
+                SmsManager.RESULT_ERROR_GENERIC_FAILURE, null, null);
+        processAllMessages();
+        //Verify if the command is encoded with correct Result byte as per TS 101.267
+        verify(mSimulatedCommands, atLeastOnce()).sendTerminalResponse(
+                eq(mTerminalResponseForSmsRpError), any());
+    }
+
+    @Test
+    public void testSendTerminalResponseForSendSmsNetworkError() {
+        setBroadcastReceiverPendingResult(mCatService.mSmsBroadcastReceiver,
+                SmsManager.RESULT_ERROR_GENERIC_FAILURE);
+        Intent intent = new Intent(SMS_SENT_ACTION).putExtra("cmdDetails", mCommandDetails);
+        intent.putExtra("ims", false);
+        intent.putExtra("errorCode", 41);
+        mContext.sendOrderedBroadcast(intent, null, mCatService.mSmsBroadcastReceiver, null,
+                SmsManager.RESULT_ERROR_GENERIC_FAILURE, null, null);
+        processAllMessages();
+        //Verify if the command is encoded with correct Result byte as per TS 101.267
+        verify(mSimulatedCommands, atLeastOnce()).sendTerminalResponse(
+                eq(mTerminalResponseForNetworkUnableToProcess), any());
+    }
+
+    @Test
+    public void testSendTerminalResponseForDeliveryFailure() {
+        setBroadcastReceiverPendingResult(mCatService.mSmsBroadcastReceiver,
+                SmsManager.RESULT_ERROR_GENERIC_FAILURE);
+        Intent intent = new Intent(SMS_DELIVERY_ACTION).putExtra("cmdDetails", mCommandDetails);
+        mContext.sendOrderedBroadcast(intent, null, mCatService.mSmsBroadcastReceiver, null,
+                SmsManager.RESULT_ERROR_GENERIC_FAILURE, null, null);
+        processAllMessages();
+        //Verify if the command is encoded with correct Result byte as per TS 101.267
+        verify(mSimulatedCommands, atLeastOnce()).sendTerminalResponse(
+                eq(mTerminalResponseForTerminalUnableToProcess), any());
+    }
+
+    @Test
+    public void testSendTerminalResponseForDeliverySuccess() {
+        setBroadcastReceiverPendingResult(mCatService.mSmsBroadcastReceiver,
+                Activity.RESULT_OK);
+        Intent intent = new Intent(SMS_DELIVERY_ACTION).putExtra("cmdDetails", mCommandDetails);
+        mContext.sendOrderedBroadcast(intent, null, mCatService.mSmsBroadcastReceiver, null,
+                Activity.RESULT_OK, null, null);
+        processAllMessages();
+        //Verify if the command is encoded with correct Result byte as per TS 101.267
+        verify(mSimulatedCommands, atLeastOnce()).sendTerminalResponse(
+                eq(mTerminalResponseForDeliverySuccess), any());
+    }
+}
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 5df94e5..3445939 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaInboundSmsHandlerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaInboundSmsHandlerTest.java
@@ -57,11 +57,6 @@
 import com.android.internal.util.IState;
 import com.android.internal.util.StateMachine;
 
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Ignore;
@@ -69,6 +64,11 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class CdmaInboundSmsHandlerTest extends TelephonyTest {
@@ -157,7 +157,7 @@
                 Telephony.Sms.CONTENT_URI.getAuthority(), mContentProvider);
 
         mCdmaInboundSmsHandler = CdmaInboundSmsHandler.makeInboundSmsHandler(mContext,
-            mSmsStorageMonitor, mPhone, null);
+            mSmsStorageMonitor, mPhone, null, mTestableLooper.getLooper());
         monitorTestableLooper(new TestableLooper(mCdmaInboundSmsHandler.getHandler().getLooper()));
         processAllMessages();
     }
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 b36a8d4..4d116a8 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/AccessNetworksManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/AccessNetworksManagerTest.java
@@ -18,7 +18,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.junit.Assume.assumeFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -33,8 +32,10 @@
 import android.content.IntentFilter;
 import android.content.pm.ServiceInfo;
 import android.net.NetworkCapabilities;
+import android.os.AsyncResult;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.Message;
 import android.os.PersistableBundle;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
@@ -120,7 +121,7 @@
         processAllMessages();
         replaceInstance(AccessNetworksManager.class, "mDataConfigManager",
                 mAccessNetworksManager, mMockedDataConfigManager);
-        assumeFalse(mAccessNetworksManager.isInLegacyMode());
+
         logd("-setUp");
     }
 
@@ -158,6 +159,32 @@
     }
 
     @Test
+    public void testGuideTransportTypeForEmergencyDataNetwork() throws Exception {
+        doAnswer(invocation -> {
+            int accessNetwork = AccessNetworkType.UNKNOWN;
+            if (invocation.getArguments()[1].equals(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)) {
+                accessNetwork = AccessNetworkType.IWLAN;
+            } else if (invocation.getArguments()[1]
+                    .equals(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)) {
+                accessNetwork = AccessNetworkType.EUTRAN;
+            }
+            mQnsCallback.onQualifiedNetworkTypesChanged(ApnSetting.TYPE_EMERGENCY,
+                    new int[]{accessNetwork});
+            return null;
+        }).when(mMockedQns).reportEmergencyDataNetworkPreferredTransportChanged(anyInt(), anyInt());
+
+        AsyncResult asyncResult =
+                new AsyncResult(null, AccessNetworkConstants.TRANSPORT_TYPE_WLAN, null);
+        Message msg = this.mAccessNetworksManager
+                .obtainMessage(1 /* EVENT_GUIDE_TRANSPORT_TYPE_FOR_EMERGENCY */, asyncResult);
+        mAccessNetworksManager.sendMessage(msg);
+        processAllMessages();
+
+        assertThat(mAccessNetworksManager.getPreferredTransport(ApnSetting.TYPE_EMERGENCY))
+                .isEqualTo(AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+    }
+
+    @Test
     public void testEmptyNetworkTypes() throws Exception {
         testQualifiedNetworkTypesChanged();
 
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 76a3d0a..428699f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/CellularNetworkValidatorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/CellularNetworkValidatorTest.java
@@ -77,7 +77,6 @@
         doReturn(CAPABILITY_WITH_VALIDATION_SUPPORTED).when(mPhoneConfigurationManager)
                 .getCurrentPhoneCapability();
         mValidatorUT = new CellularNetworkValidator(mContext);
-        doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt());
         doReturn(new SubscriptionInfoInternal.Builder().setSimSlotIndex(0).setId(1).build())
                 .when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
         processAllMessages();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
index d8b93f5..808a15d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony.data;
 
+import static android.telephony.TelephonyManager.HAL_SERVICE_DATA;
+
 import static com.android.internal.telephony.data.DataNetworkController.DataNetworkControllerCallback;
 import static com.android.internal.telephony.data.DataNetworkController.NetworkRequestList;
 
@@ -142,6 +144,12 @@
     private static final String FAKE_MMTEL_PACKAGE = "fake.mmtel.package";
     private static final String FAKE_RCS_PACKAGE = "fake.rcs.package";
 
+    // Events
+    private static final int EVENT_SIM_STATE_CHANGED = 9;
+    private static final int EVENT_REEVALUATE_EXISTING_DATA_NETWORKS = 16;
+    private static final int EVENT_VOICE_CALL_ENDED = 18;
+    private static final int EVENT_SUBSCRIPTION_OVERRIDE = 23;
+
     // Mocked classes
     private PhoneSwitcher mMockedPhoneSwitcher;
     protected ISub mMockedIsub;
@@ -228,6 +236,36 @@
             .setPreferred(false)
             .build();
 
+    // Created to test preferred data profiles that apply to different network types
+    private final DataProfile mGeneralPurposeDataProfileAlternative = new DataProfile.Builder()
+            .setApnSetting(new ApnSetting.Builder()
+                    .setId(2161)
+                    .setOperatorNumeric("12345")
+                    .setEntryName("internet_supl_mms_apn")
+                    .setApnName("internet_supl_mms_apn")
+                    .setUser("user")
+                    .setPassword("passwd")
+                    .setApnTypeBitmask(ApnSetting.TYPE_DEFAULT | ApnSetting.TYPE_SUPL
+                            | ApnSetting.TYPE_MMS)
+                    .setProtocol(ApnSetting.PROTOCOL_IPV6)
+                    .setRoamingProtocol(ApnSetting.PROTOCOL_IP)
+                    .setCarrierEnabled(true)
+                    .setNetworkTypeBitmask((int) (TelephonyManager.NETWORK_TYPE_BITMASK_LTE
+                            | TelephonyManager.NETWORK_TYPE_BITMASK_NR
+                            | TelephonyManager.NETWORK_TYPE_BITMASK_IWLAN
+                            | TelephonyManager.NETWORK_TYPE_BITMASK_1xRTT))
+                    .setLingeringNetworkTypeBitmask((int) (TelephonyManager.NETWORK_TYPE_BITMASK_LTE
+                            | TelephonyManager.NETWORK_TYPE_BITMASK_1xRTT
+                            | TelephonyManager.NETWORK_TYPE_BITMASK_UMTS
+                            | TelephonyManager.NETWORK_TYPE_BITMASK_NR))
+                    .setProfileId(4321)
+                    .setMaxConns(321)
+                    .setWaitTime(456)
+                    .setMaxConnsTime(789)
+                    .build())
+            .setPreferred(false)
+            .build();
+
     private final DataProfile mImsCellularDataProfile = new DataProfile.Builder()
             .setApnSetting(new ApnSetting.Builder()
                     .setId(2164)
@@ -319,7 +357,7 @@
                     .setApnName("dun_apn")
                     .setUser("user")
                     .setPassword("passwd")
-                    .setApnTypeBitmask(ApnSetting.TYPE_DUN)
+                    .setApnTypeBitmask(ApnSetting.TYPE_DUN | ApnSetting.TYPE_DEFAULT)
                     .setProtocol(ApnSetting.PROTOCOL_IPV6)
                     .setRoamingProtocol(ApnSetting.PROTOCOL_IP)
                     .setCarrierEnabled(true)
@@ -516,9 +554,13 @@
 
     private void serviceStateChanged(@NetworkType int networkType,
             @RegistrationState int regState) {
-        DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo(8, false, true, true,
-                new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_SUPPORTED,
-                        LteVopsSupportInfo.LTE_STATUS_SUPPORTED));
+        DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8)
+                .setNrAvailable(true)
+                .setEnDcAvailable(true)
+                .setVopsSupportInfo(new LteVopsSupportInfo(
+                        LteVopsSupportInfo.LTE_STATUS_SUPPORTED,
+                        LteVopsSupportInfo.LTE_STATUS_SUPPORTED))
+                .build();
 
         serviceStateChanged(networkType, regState, regState,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri);
@@ -548,9 +590,13 @@
             @RegistrationState int dataRegState, @RegistrationState int voiceRegState,
             @RegistrationState int iwlanRegState, DataSpecificRegistrationInfo dsri) {
         if (dsri == null) {
-            dsri = new DataSpecificRegistrationInfo(8, false, true, true,
-                    new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_SUPPORTED,
-                            LteVopsSupportInfo.LTE_STATUS_SUPPORTED));
+            dsri = new DataSpecificRegistrationInfo.Builder(8)
+                    .setNrAvailable(true)
+                    .setEnDcAvailable(true)
+                    .setVopsSupportInfo(new LteVopsSupportInfo(
+                            LteVopsSupportInfo.LTE_STATUS_SUPPORTED,
+                            LteVopsSupportInfo.LTE_STATUS_SUPPORTED))
+                    .build();
         }
 
         ServiceState ss = new ServiceState();
@@ -725,20 +771,12 @@
         doReturn(true).when(mSST).getPowerStateFromCarrier();
         doReturn(true).when(mSST).isConcurrentVoiceAndDataAllowed();
         doReturn(PhoneConstants.State.IDLE).when(mCT).getState();
-        doReturn("").when(mSubscriptionController).getEnabledMobileDataPolicies(anyInt());
-        doReturn(true).when(mSubscriptionController).setEnabledMobileDataPolicies(
-                anyInt(), anyString());
         doReturn(new SubscriptionInfoInternal.Builder().setId(1).build())
                 .when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
 
         List<SubscriptionInfo> infoList = new ArrayList<>();
         infoList.add(mMockSubInfo);
-        doReturn(infoList).when(mSubscriptionController).getSubscriptionsInGroup(
-                any(), any(), any());
-        doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt());
-        doReturn(0).when(mSubscriptionController).getPhoneId(1);
         doReturn(0).when(mSubscriptionManagerService).getPhoneId(1);
-        doReturn(1).when(mSubscriptionController).getPhoneId(2);
         doReturn(1).when(mSubscriptionManagerService).getPhoneId(2);
 
         for (int transport : new int[]{AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
@@ -830,7 +868,8 @@
                 linkBandwidthEstimatorCallbackCaptor.capture());
         mLinkBandwidthEstimatorCallback = linkBandwidthEstimatorCallbackCaptor.getValue();
 
-        List<DataProfile> profiles = List.of(mGeneralPurposeDataProfile, mImsCellularDataProfile,
+        List<DataProfile> profiles = List.of(mGeneralPurposeDataProfile,
+                mGeneralPurposeDataProfileAlternative, mImsCellularDataProfile,
                 mImsIwlanDataProfile, mEmergencyDataProfile, mFotaDataProfile,
                 mTetheringDataProfile);
 
@@ -883,7 +922,6 @@
 
         doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WWAN).when(mAccessNetworksManager)
                 .getPreferredTransportByNetworkCapability(anyInt());
-        doReturn(true).when(mDataProfileManager).isDataProfilePreferred(any(DataProfile.class));
 
         doAnswer(invocation -> {
             ((Runnable) invocation.getArguments()[0]).run();
@@ -897,7 +935,7 @@
         mDataNetworkControllerUT.registerDataNetworkControllerCallback(
                 mMockedDataNetworkControllerCallback);
 
-        mDataNetworkControllerUT.obtainMessage(9/*EVENT_SIM_STATE_CHANGED*/,
+        mDataNetworkControllerUT.obtainMessage(EVENT_SIM_STATE_CHANGED,
                 10/*SIM_STATE_LOADED*/, 0).sendToTarget();
         mDataNetworkControllerUT.obtainMessage(8/*EVENT_DATA_SERVICE_BINDING_CHANGED*/,
                 new AsyncResult(AccessNetworkConstants.TRANSPORT_TYPE_WWAN, true, null))
@@ -1257,12 +1295,14 @@
     public void testSimRemovalDataTearDown() throws Exception {
         testSetupDataNetwork();
 
-        mDataNetworkControllerUT.obtainMessage(9/*EVENT_SIM_STATE_CHANGED*/,
+        mDataNetworkControllerUT.obtainMessage(EVENT_SIM_STATE_CHANGED,
                 TelephonyManager.SIM_STATE_ABSENT, 0).sendToTarget();
         processAllMessages();
         verifyAllDataDisconnected();
         verify(mMockedDataNetworkControllerCallback).onAnyDataNetworkExistingChanged(eq(false));
         verify(mMockedDataNetworkControllerCallback).onInternetDataNetworkDisconnected();
+        verify(mMockedDataNetworkControllerCallback).onPhysicalLinkStatusChanged(
+                eq(DataCallResponse.LINK_STATUS_INACTIVE));
     }
 
     @Test
@@ -1271,7 +1311,7 @@
         Mockito.clearInvocations(mMockedDataNetworkControllerCallback);
 
         // Insert the SIM again.
-        mDataNetworkControllerUT.obtainMessage(9/*EVENT_SIM_STATE_CHANGED*/,
+        mDataNetworkControllerUT.obtainMessage(EVENT_SIM_STATE_CHANGED,
                 TelephonyManager.SIM_STATE_LOADED, 0).sendToTarget();
         processAllMessages();
 
@@ -1425,7 +1465,8 @@
         verifyAllDataDisconnected();
         verify(mMockedDataNetworkControllerCallback).onAnyDataNetworkExistingChanged(eq(false));
         verify(mMockedDataNetworkControllerCallback).onInternetDataNetworkDisconnected();
-
+        verify(mMockedDataNetworkControllerCallback).onPhysicalLinkStatusChanged(
+                eq(DataCallResponse.LINK_STATUS_INACTIVE));
 
         Mockito.clearInvocations(mMockedDataNetworkControllerCallback);
         // Now RAT changes from GSM to UMTS
@@ -1480,7 +1521,7 @@
 
         // Call ended.
         doReturn(PhoneConstants.State.IDLE).when(mCT).getState();
-        mDataNetworkControllerUT.obtainMessage(18/*EVENT_VOICE_CALL_ENDED*/).sendToTarget();
+        mDataNetworkControllerUT.obtainMessage(EVENT_VOICE_CALL_ENDED).sendToTarget();
         processAllMessages();
 
         // It should have no internet setup at the beginning.
@@ -1585,9 +1626,7 @@
         boolean isDataEnabled = mDataNetworkControllerUT.getDataSettingsManager().isDataEnabled();
         doReturn(mDataNetworkControllerUT.getDataSettingsManager())
                 .when(mPhone).getDataSettingsManager();
-        MultiSimSettingController instance = MultiSimSettingController.getInstance();
-        MultiSimSettingController controller = Mockito.spy(
-                new MultiSimSettingController(mContext, mSubscriptionController));
+        MultiSimSettingController controller = Mockito.spy(new MultiSimSettingController(mContext));
         doReturn(true).when(controller).isCarrierConfigLoadedForAllSub();
         replaceInstance(MultiSimSettingController.class, "sInstance", null, controller);
 
@@ -1637,6 +1676,14 @@
         verifyNoConnectedNetworkHasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
         verifyNoConnectedNetworkHasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL);
 
+        mDataNetworkControllerUT.obtainMessage(16 /*EVENT_REEVALUATE_EXISTING_DATA_NETWORKS*/,
+                DataEvaluation.DataEvaluationReason.DATA_SERVICE_STATE_CHANGED).sendToTarget();
+
+        processAllFutureMessages();
+
+        // Make sure IMS network is not torn down
+        verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_MMS);
+
         // Remove MMS data enabled override
         mDataNetworkControllerUT.getDataSettingsManager().setMobileDataPolicy(TelephonyManager
                 .MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED, false);
@@ -1673,7 +1720,6 @@
     @Test
     public void testIsDataEnabledOverriddenForApnDataDuringCall() throws Exception {
         doReturn(1).when(mPhone).getSubId();
-        doReturn(2).when(mSubscriptionController).getDefaultDataSubId();
         doReturn(2).when(mSubscriptionManagerService).getDefaultDataSubId();
         // Data disabled
         mDataNetworkControllerUT.getDataSettingsManager().setDataEnabled(
@@ -1716,7 +1762,6 @@
         // Assume phone2 is the default data phone
         Phone phone2 = Mockito.mock(Phone.class);
         replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[]{mPhone, phone2});
-        doReturn(2).when(mSubscriptionController).getDefaultDataSubId();
         doReturn(2).when(mSubscriptionManagerService).getDefaultDataSubId();
 
         // Data disabled on nonDDS
@@ -1746,9 +1791,20 @@
         // Verify internet connection
         verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_INTERNET);
 
-        // Disable auto data switch mobile policy
+        // Disable auto data switch mobile policy, but enabled data during call
         mDataNetworkControllerUT.getDataSettingsManager().setMobileDataPolicy(TelephonyManager
                 .MOBILE_DATA_POLICY_AUTO_DATA_SWITCH, false);
+        mDataNetworkControllerUT.getDataSettingsManager().setMobileDataPolicy(TelephonyManager
+                .MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL, true);
+        doReturn(PhoneConstants.State.RINGING).when(phone2).getState();
+        processAllMessages();
+
+        // Verify internet connection
+        verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+
+        // Disable data during call
+        mDataNetworkControllerUT.getDataSettingsManager().setMobileDataPolicy(TelephonyManager
+                .MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL, false);
         processAllMessages();
 
         // Verify no internet connection
@@ -1939,7 +1995,7 @@
 
         // Set 5G unmetered
         congestedNetworkTypes.add(TelephonyManager.NETWORK_TYPE_NR);
-        mDataNetworkControllerUT.obtainMessage(23/*EVENT_SUBSCRIPTION_OVERRIDE*/,
+        mDataNetworkControllerUT.obtainMessage(EVENT_SUBSCRIPTION_OVERRIDE,
                 NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_CONGESTED,
                 NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_CONGESTED,
                 new int[]{TelephonyManager.NETWORK_TYPE_NR}).sendToTarget();
@@ -1961,7 +2017,7 @@
 
         // Set all network types metered
         congestedNetworkTypes.clear();
-        mDataNetworkControllerUT.obtainMessage(23/*EVENT_SUBSCRIPTION_OVERRIDE*/,
+        mDataNetworkControllerUT.obtainMessage(EVENT_SUBSCRIPTION_OVERRIDE,
                 NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_CONGESTED, 0,
                 TelephonyManager.getAllNetworkTypes()).sendToTarget();
         dataNetwork.sendMessage(16/*EVENT_SUBSCRIPTION_PLAN_OVERRIDE*/);
@@ -1981,7 +2037,7 @@
 
         // Set 5G unmetered
         unmeteredNetworkTypes.add(TelephonyManager.NETWORK_TYPE_NR);
-        mDataNetworkControllerUT.obtainMessage(23/*EVENT_SUBSCRIPTION_OVERRIDE*/,
+        mDataNetworkControllerUT.obtainMessage(EVENT_SUBSCRIPTION_OVERRIDE,
                 NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_UNMETERED,
                 NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_UNMETERED,
                 new int[]{TelephonyManager.NETWORK_TYPE_NR}).sendToTarget();
@@ -2005,7 +2061,7 @@
 
         // Set all network types metered
         unmeteredNetworkTypes.clear();
-        mDataNetworkControllerUT.obtainMessage(23/*EVENT_SUBSCRIPTION_OVERRIDE*/,
+        mDataNetworkControllerUT.obtainMessage(EVENT_SUBSCRIPTION_OVERRIDE,
                 NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_UNMETERED, 0,
                 TelephonyManager.getAllNetworkTypes()).sendToTarget();
         dataNetwork.sendMessage(16/*EVENT_SUBSCRIPTION_PLAN_OVERRIDE*/);
@@ -2100,7 +2156,7 @@
                 NetworkCapabilities.NET_CAPABILITY_MMTEL);
 
         // Both internet and IMS should be retained after network re-evaluation
-        mDataNetworkControllerUT.obtainMessage(16/*EVENT_REEVALUATE_EXISTING_DATA_NETWORKS*/)
+        mDataNetworkControllerUT.obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS)
                 .sendToTarget();
         processAllMessages();
 
@@ -2120,7 +2176,7 @@
                 NetworkCapabilities.NET_CAPABILITY_MMTEL);
 
         // Both internet and IMS should be retained after network re-evaluation
-        mDataNetworkControllerUT.obtainMessage(16/*EVENT_REEVALUATE_EXISTING_DATA_NETWORKS*/)
+        mDataNetworkControllerUT.obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS)
                 .sendToTarget();
         processAllMessages();
 
@@ -2284,6 +2340,8 @@
 
         // 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();
@@ -2344,6 +2402,8 @@
 
         // Verify all data disconnected.
         verify(mMockedDataNetworkControllerCallback).onAnyDataNetworkExistingChanged(eq(false));
+        verify(mMockedDataNetworkControllerCallback).onPhysicalLinkStatusChanged(
+                eq(DataCallResponse.LINK_STATUS_INACTIVE));
 
         // Should setup a new one instead of handover.
         verify(mMockedWwanDataServiceManager).setupDataCall(anyInt(), any(DataProfile.class),
@@ -2455,6 +2515,7 @@
     public void testHandoverDataNetworkRetryReachedMaximum() throws Exception {
         testSetupImsDataNetwork();
 
+        // 1. Normal case
         setFailedSetupDataResponse(mMockedWlanDataServiceManager,
                 DataFailCause.HANDOVER_FAILED, -1, true);
         updateTransport(NetworkCapabilities.NET_CAPABILITY_IMS,
@@ -2475,6 +2536,30 @@
         verify(mMockedWlanDataServiceManager).setupDataCall(anyInt(), any(DataProfile.class),
                 anyBoolean(), anyBoolean(), eq(DataService.REQUEST_REASON_NORMAL), any(), anyInt(),
                 any(), any(), anyBoolean(), any(Message.class));
+
+        // 2. Active VoPS call, should delay tear down
+        doReturn(PhoneConstants.State.RINGING).when(mCT).getState();
+        mCarrierConfig.putBoolean(CarrierConfigManager.KEY_DELAY_IMS_TEAR_DOWN_UNTIL_CALL_END_BOOL,
+                true);
+        carrierConfigChanged();
+
+        setFailedSetupDataResponse(mMockedWwanDataServiceManager,
+                DataFailCause.HANDOVER_FAILED, -1, true);
+        updateTransport(NetworkCapabilities.NET_CAPABILITY_IMS,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        processAllFutureMessages();
+
+        // Verify the network wasn't torn down
+        verify(mMockedWlanDataServiceManager, never()).deactivateDataCall(anyInt(),
+                eq(DataService.REQUEST_REASON_NORMAL), any(Message.class));
+
+        // Verify tear down after call ends
+        doReturn(PhoneConstants.State.IDLE).when(mCT).getState();
+        mDataNetworkControllerUT.obtainMessage(EVENT_VOICE_CALL_ENDED).sendToTarget();
+        processAllFutureMessages();
+
+        verify(mMockedWlanDataServiceManager).deactivateDataCall(anyInt(),
+                eq(DataService.REQUEST_REASON_NORMAL), any(Message.class));
     }
 
     @Test
@@ -2625,8 +2710,9 @@
                 createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_INTERNET));
         processAllFutureMessages();
 
-        // Should retried 20 times, which is the maximum based on the retry config rules.
-        verify(mMockedWwanDataServiceManager, times(21)).setupDataCall(anyInt(),
+        // The first 8 retries are short timers that scheduled by handler, future retries are
+        // scheduled by intent and require more complex mock, so we only verify the first 8 here.
+        verify(mMockedWwanDataServiceManager, times(9)).setupDataCall(anyInt(),
                 any(DataProfile.class), anyBoolean(), anyBoolean(), anyInt(), any(), anyInt(),
                 any(), any(), anyBoolean(), any(Message.class));
     }
@@ -2776,7 +2862,7 @@
         processAllFutureMessages();
 
         // TAC changes should clear the already-scheduled retry and throttling.
-        assertThat(mDataNetworkControllerUT.getDataRetryManager().isAnySetupRetryScheduled(
+        assertThat(mDataNetworkControllerUT.getDataRetryManager().isDataProfileThrottled(
                 mImsCellularDataProfile, AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).isFalse();
 
         // But DNC should re-evaluate unsatisfied request and setup IMS again.
@@ -2799,8 +2885,8 @@
         // There should be only one attempt, and no retry should happen because it's a permanent
         // failure.
         verify(mMockedWwanDataServiceManager, times(1)).setupDataCall(anyInt(),
-                any(DataProfile.class), anyBoolean(), anyBoolean(), anyInt(), any(), anyInt(),
-                any(), any(), anyBoolean(), any(Message.class));
+                eq(mGeneralPurposeDataProfile), anyBoolean(), anyBoolean(), anyInt(), any(),
+                anyInt(), any(), any(), anyBoolean(), any(Message.class));
 
         Mockito.clearInvocations(mMockedWwanDataServiceManager);
         mDataNetworkControllerUT.addNetworkRequest(
@@ -2881,6 +2967,35 @@
     }
 
     @Test
+    public void testHandoverDataNetworkNetworkSuggestedRetryTimerDataThrottled() throws Exception {
+        testSetupImsDataNetwork();
+
+        DataNetwork network = getDataNetworks().get(0);
+        setFailedSetupDataResponse(mMockedWlanDataServiceManager,
+                DataFailCause.HANDOVER_FAILED, 10000, true);
+        updateTransport(NetworkCapabilities.NET_CAPABILITY_IMS,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+        // Verify retry scheduled on this network
+        assertThat(mDataNetworkControllerUT.getDataRetryManager()
+                .isAnyHandoverRetryScheduled(network)).isTrue();
+        // Verify the data profile is throttled on WLAN
+        assertThat(mDataNetworkControllerUT.getDataRetryManager().isDataProfileThrottled(
+                network.getDataProfile(), AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).isTrue();
+
+        // Test even if network disconnected, the throttle status should remain
+        network.tearDown(DataNetwork.TEAR_DOWN_REASON_CONNECTIVITY_SERVICE_UNWANTED);
+        processAllFutureMessages();
+
+        // Verify retry is cleared on this network
+        assertThat(mDataNetworkControllerUT.getDataRetryManager()
+                .isAnyHandoverRetryScheduled(network)).isFalse();
+        // Verify the data profile is still throttled
+        assertThat(mDataNetworkControllerUT.getDataRetryManager().isDataProfileThrottled(
+                network.getDataProfile(), AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).isTrue();
+    }
+
+    @Test
     public void testTacChangesClearThrottlingAndRetryHappens() throws Exception {
         testSetupDataNetworkNetworkSuggestedRetryTimerDataThrottled();
         processAllFutureMessages();
@@ -2893,7 +3008,7 @@
         processAllFutureMessages();
 
         // TAC changes should clear the already-scheduled retry and throttling.
-        assertThat(mDataNetworkControllerUT.getDataRetryManager().isAnySetupRetryScheduled(
+        assertThat(mDataNetworkControllerUT.getDataRetryManager().isDataProfileThrottled(
                 mImsCellularDataProfile, AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).isFalse();
 
         // But DNC should re-evaluate unsatisfied request and setup IMS again.
@@ -3115,6 +3230,73 @@
     }
 
     @Test
+    public void testSetPreferredDataProfileMultiInternetDataProfile() throws Exception {
+        // No preferred data profile in the beginning
+        doReturn(false).when(mDataProfileManager).canPreferredDataProfileSatisfy(
+                any(NetworkRequestList.class));
+
+        testSetupDataNetwork();
+
+        // Verify this network still alive after evaluation
+        mDataNetworkControllerUT.obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS)
+                .sendToTarget();
+        processAllMessages();
+
+        verifyConnectedNetworkHasDataProfile(mGeneralPurposeDataProfile);
+
+        // Network connected, became preferred data profile
+        doAnswer(invocation -> {
+            NetworkRequestList networkRequests =
+                    (NetworkRequestList) invocation.getArguments()[0];
+            return networkRequests.stream()
+                    .allMatch(request -> request.canBeSatisfiedBy(mGeneralPurposeDataProfile));
+        }).when(mDataProfileManager).canPreferredDataProfileSatisfy(
+                any(NetworkRequestList.class));
+        doReturn(true).when(mDataProfileManager)
+                .isDataProfilePreferred(mGeneralPurposeDataProfile);
+
+        // 1. Test DUN | DEFAULT data profile is compatible with preferred default internet
+        mDataNetworkControllerUT.addNetworkRequest(
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_DUN));
+        setSuccessfulSetupDataResponse(mMockedWwanDataServiceManager, 2);
+        processAllMessages();
+
+        // Verify both DUN and preferred default network are alive
+        verifyConnectedNetworkHasDataProfile(mGeneralPurposeDataProfile);
+        verifyConnectedNetworkHasDataProfile(mTetheringDataProfile);
+
+        // Verify this network still alive after evaluation
+        mDataNetworkControllerUT.obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS)
+                .sendToTarget();
+        processAllMessages();
+
+        verifyConnectedNetworkHasDataProfile(mGeneralPurposeDataProfile);
+        verifyConnectedNetworkHasDataProfile(mTetheringDataProfile);
+
+        // 2. Test tear down when user changes preferred data profile
+        doAnswer(invocation -> {
+            NetworkRequestList networkRequests =
+                    (NetworkRequestList) invocation.getArguments()[0];
+            return networkRequests.stream()
+                    .allMatch(request -> request.canBeSatisfiedBy(
+                            mGeneralPurposeDataProfileAlternative));
+        }).when(mDataProfileManager).canPreferredDataProfileSatisfy(
+                any(NetworkRequestList.class));
+        doReturn(true).when(mDataProfileManager)
+                .isDataProfilePreferred(mGeneralPurposeDataProfileAlternative);
+        doReturn(false).when(mDataProfileManager)
+                .isDataProfilePreferred(mGeneralPurposeDataProfile);
+
+        mDataNetworkControllerUT.obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS)
+                .sendToTarget();
+        processAllMessages();
+
+        List<DataNetwork> dataNetworks = getDataNetworks();
+        assertThat(dataNetworks).hasSize(1);
+        verifyConnectedNetworkHasDataProfile(mTetheringDataProfile);
+    }
+
+    @Test
     public void testDataDisableNotAllowingBringingUpTetheringNetwork() throws Exception {
         // User data disabled
         mDataNetworkControllerUT.getDataSettingsManager().setDataEnabled(
@@ -3181,9 +3363,13 @@
     }
     @Test
     public void testNonVoPSNoIMSSetup() throws Exception {
-        DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo(8, false, true, true,
-                new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED,
-                        LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED));
+        DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8)
+                .setNrAvailable(true)
+                .setEnDcAvailable(true)
+                .setVopsSupportInfo(new LteVopsSupportInfo(
+                        LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED,
+                        LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED))
+                .build();
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri);
 
@@ -3203,9 +3389,13 @@
         carrierConfigChanged();
 
         // VOPS not supported
-        DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo(8, false, true, true,
-                new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED,
-                        LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED));
+        DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8)
+                .setNrAvailable(true)
+                .setEnDcAvailable(true)
+                .setVopsSupportInfo(new LteVopsSupportInfo(
+                        LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED,
+                        LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED))
+                .build();
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri);
 
@@ -3216,9 +3406,13 @@
         verifyNoConnectedNetworkHasCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
 
         // VoPS supported
-        dsri = new DataSpecificRegistrationInfo(8, false, true, true,
-                new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_SUPPORTED,
-                        LteVopsSupportInfo.LTE_STATUS_SUPPORTED));
+        dsri = new DataSpecificRegistrationInfo.Builder(8)
+                .setNrAvailable(true)
+                .setEnDcAvailable(true)
+                .setVopsSupportInfo(new LteVopsSupportInfo(
+                        LteVopsSupportInfo.LTE_STATUS_SUPPORTED,
+                        LteVopsSupportInfo.LTE_STATUS_SUPPORTED))
+                .build();
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri);
 
@@ -3398,11 +3592,11 @@
         verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_DUN);
 
         mDataNetworkControllerUT.addNetworkRequest(
-                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE));
         processAllFutureMessages();
         // Lower priority network should not trump the higher priority network.
         verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_DUN);
-        verifyNoConnectedNetworkHasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        verifyNoConnectedNetworkHasCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE);
 
         // Now send a higher priority network request
         TelephonyNetworkRequest fotaRequest = createNetworkRequest(
@@ -3574,38 +3768,63 @@
 
     @Test
     public void testHandoverDataNetworkOos() throws Exception {
-        ServiceState ss = new ServiceState();
-        ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
-                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
-                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
-                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
-                .build());
+        // Config delay IMS tear down enabled
+        mCarrierConfig.putBoolean(CarrierConfigManager.KEY_DELAY_IMS_TEAR_DOWN_UNTIL_CALL_END_BOOL,
+                true);
+        carrierConfigChanged();
 
-        ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
-                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
-                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_IWLAN)
-                .setRegistrationState(
-                        NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING)
-                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
-                .build());
-
-        ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
-                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
-                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
-                .setDomain(NetworkRegistrationInfo.DOMAIN_CS)
-                .build());
-        processServiceStateRegStateForTest(ss);
-        doReturn(ss).when(mSST).getServiceState();
-        doReturn(ss).when(mPhone).getServiceState();
-
-        mDataNetworkControllerUT.obtainMessage(17/*EVENT_SERVICE_STATE_CHANGED*/).sendToTarget();
-        processAllMessages();
+        // VoPS supported
+        DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8)
+                .setNrAvailable(true)
+                .setEnDcAvailable(true)
+                .setVopsSupportInfo(new LteVopsSupportInfo(
+                        LteVopsSupportInfo.LTE_STATUS_SUPPORTED,
+                        LteVopsSupportInfo.LTE_STATUS_SUPPORTED))
+                .build();
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME /*data*/,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME /*voice*/,
+                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING /*iwlan*/,
+                dsri);
 
         testSetupImsDataNetwork();
+        DataNetwork dataNetwork = getDataNetworks().get(0);
+
+        // 1. Active VoPS call, mock target IWLAN OOS, should schedule retry
+        doReturn(PhoneConstants.State.RINGING).when(mCT).getState();
         updateTransport(NetworkCapabilities.NET_CAPABILITY_IMS,
                 AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+        // Process DRM event to schedule retry
+        processAllMessages();
+
+        // Verify scheduled new handover retry
+        assertTrue(mDataNetworkControllerUT.getDataRetryManager()
+                .isAnyHandoverRetryScheduled(dataNetwork));
+        // Verify the network wasn't torn down
+        verify(mMockedWwanDataServiceManager, never()).deactivateDataCall(anyInt(),
+                eq(DataService.REQUEST_REASON_NORMAL), any(Message.class));
+
+        // Get the scheduled retry
+        Field field = DataRetryManager.class.getDeclaredField("mDataRetryEntries");
+        field.setAccessible(true);
+        DataRetryManager.DataHandoverRetryEntry dataRetryEntry =
+                (DataRetryManager.DataHandoverRetryEntry) ((List<DataRetryManager.DataRetryEntry>)
+                        field.get(mDataNetworkControllerUT.getDataRetryManager())).get(0);
+
+        // Process the retry
+        moveTimeForward(1000 /*The retry delay of the first attempt*/);
+        processAllMessages();
+
+        // Verify the previous retry is set to FAILED
+        assertEquals(DataRetryManager.DataRetryEntry.RETRY_STATE_FAILED, dataRetryEntry.getState());
+        // Verify a new retry is scheduled
+        assertTrue(mDataNetworkControllerUT.getDataRetryManager()
+                .isAnyHandoverRetryScheduled(dataNetwork));
+
+        // 2. Normal case (call ended), should tear down
+        doReturn(PhoneConstants.State.IDLE).when(mCT).getState();
+        mDataNetworkControllerUT.obtainMessage(EVENT_VOICE_CALL_ENDED).sendToTarget();
+        processAllFutureMessages();
 
         // Verify that handover is not performed.
         verify(mMockedWlanDataServiceManager, never()).setupDataCall(anyInt(),
@@ -3613,7 +3832,7 @@
                 eq(DataService.REQUEST_REASON_NORMAL), any(), anyInt(), any(), any(), anyBoolean(),
                 any(Message.class));
 
-        // IMS network should be torn down.
+        // Verify IMS network should be torn down.
         verifyAllDataDisconnected();
     }
 
@@ -3685,9 +3904,13 @@
     public void testHandoverDataNetworkNonVops() throws Exception {
         ServiceState ss = new ServiceState();
 
-        DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo(8, false, true, true,
-                new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED,
-                        LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED));
+        DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8)
+                .setNrAvailable(true)
+                .setEnDcAvailable(true)
+                .setVopsSupportInfo(new LteVopsSupportInfo(
+                        LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED,
+                        LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED))
+                .build();
 
         ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
                 .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
@@ -3748,9 +3971,13 @@
 
         ServiceState ss = new ServiceState();
 
-        DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo(8, false, true, true,
-                new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED,
-                        LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED));
+        DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8)
+                .setNrAvailable(true)
+                .setEnDcAvailable(true)
+                .setVopsSupportInfo(new LteVopsSupportInfo(
+                        LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED,
+                        LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED))
+                .build();
 
         ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
                 .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
@@ -3808,9 +4035,13 @@
     public void testNonMmtelImsHandoverDataNetworkNonVops() throws Exception {
         ServiceState ss = new ServiceState();
 
-        DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo(8, false, true, true,
-                new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED,
-                        LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED));
+        DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8)
+                .setNrAvailable(true)
+                .setEnDcAvailable(true)
+                .setVopsSupportInfo(new LteVopsSupportInfo(
+                        LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED,
+                        LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED))
+                .build();
 
         ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
                 .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
@@ -3876,9 +4107,13 @@
         ServiceState ss = new ServiceState();
 
         // VoPS network
-        DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo(8, false, true, true,
-                new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_SUPPORTED,
-                        LteVopsSupportInfo.LTE_STATUS_SUPPORTED));
+        DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8)
+                .setNrAvailable(true)
+                .setEnDcAvailable(true)
+                .setVopsSupportInfo(new LteVopsSupportInfo(
+                        LteVopsSupportInfo.LTE_STATUS_SUPPORTED,
+                        LteVopsSupportInfo.LTE_STATUS_SUPPORTED))
+                .build();
 
         ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
                 .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
@@ -3923,9 +4158,13 @@
 
         ss = new ServiceState();
         // Non VoPS network
-        dsri = new DataSpecificRegistrationInfo(8, false, true, true,
-                new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED,
-                        LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED));
+        dsri = new DataSpecificRegistrationInfo.Builder(8)
+                .setNrAvailable(true)
+                .setEnDcAvailable(true)
+                .setVopsSupportInfo(new LteVopsSupportInfo(
+                        LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED,
+                        LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED))
+                .build();
 
         ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
                 .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
@@ -3961,7 +4200,7 @@
 
         mCarrierConfig.putBoolean(CarrierConfigManager.Ims.KEY_KEEP_PDN_UP_IN_NO_VOPS_BOOL, false);
         carrierConfigChanged();
-        mDataNetworkControllerUT.obtainMessage(16/*EVENT_REEVALUATE_EXISTING_DATA_NETWORKS*/)
+        mDataNetworkControllerUT.obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS)
                 .sendToTarget();
         processAllMessages();
 
@@ -3977,9 +4216,13 @@
         carrierConfigChanged();
 
         // VoPS supported
-        DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo(8, false, true, true,
-                new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_SUPPORTED,
-                        LteVopsSupportInfo.LTE_STATUS_SUPPORTED));
+        DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8)
+                .setNrAvailable(true)
+                .setEnDcAvailable(true)
+                .setVopsSupportInfo(new LteVopsSupportInfo(
+                        LteVopsSupportInfo.LTE_STATUS_SUPPORTED,
+                        LteVopsSupportInfo.LTE_STATUS_SUPPORTED))
+                .build();
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri);
 
@@ -3991,9 +4234,13 @@
 
         doReturn(PhoneConstants.State.OFFHOOK).when(mCT).getState();
 
-        dsri = new DataSpecificRegistrationInfo(8, false, true, true,
-                new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED,
-                        LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED));
+        dsri = new DataSpecificRegistrationInfo.Builder(8)
+                .setNrAvailable(true)
+                .setEnDcAvailable(true)
+                .setVopsSupportInfo(new LteVopsSupportInfo(
+                        LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED,
+                        LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED))
+                .build();
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri);
 
@@ -4003,7 +4250,7 @@
 
         // Call ends
         doReturn(PhoneConstants.State.IDLE).when(mCT).getState();
-        mDataNetworkControllerUT.obtainMessage(18/*EVENT_VOICE_CALL_ENDED*/).sendToTarget();
+        mDataNetworkControllerUT.obtainMessage(EVENT_VOICE_CALL_ENDED).sendToTarget();
         processAllMessages();
 
         verifyNoConnectedNetworkHasCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
@@ -4019,16 +4266,18 @@
         }).when(mMockedWwanDataServiceManager).deactivateDataCall(
                 anyInt(), anyInt(), any(Message.class));
         // Simulate old devices
-        doReturn(RIL.RADIO_HAL_VERSION_1_6).when(mPhone).getHalVersion();
+        doReturn(RIL.RADIO_HAL_VERSION_1_6).when(mPhone).getHalVersion(HAL_SERVICE_DATA);
 
         testSetupDataNetwork();
 
-        mDataNetworkControllerUT.obtainMessage(9/*EVENT_SIM_STATE_CHANGED*/,
+        mDataNetworkControllerUT.obtainMessage(EVENT_SIM_STATE_CHANGED,
                 TelephonyManager.SIM_STATE_ABSENT, 0).sendToTarget();
         processAllMessages();
         verifyAllDataDisconnected();
         verify(mMockedDataNetworkControllerCallback).onAnyDataNetworkExistingChanged(eq(false));
         verify(mMockedDataNetworkControllerCallback).onInternetDataNetworkDisconnected();
+        verify(mMockedDataNetworkControllerCallback).onPhysicalLinkStatusChanged(
+                eq(DataCallResponse.LINK_STATUS_INACTIVE));
     }
 
     @Test
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 1650148..b85081f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
@@ -202,30 +202,8 @@
         doAnswer(invocation -> {
             final Message msg = (Message) invocation.getArguments()[10];
 
-            DataCallResponse response = new DataCallResponse.Builder()
-                    .setCause(0)
-                    .setRetryDurationMillis(-1L)
-                    .setId(cid)
-                    .setLinkStatus(2)
-                    .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(new ArrayList<>())
-                    .setTrafficDescriptors(tds)
-                    .build();
+            DataCallResponse response = createDataCallResponse(
+                    cid, DataCallResponse.LINK_STATUS_ACTIVE, tds);
             msg.getData().putParcelable("data_call_response", response);
             msg.arg1 = DataServiceCallback.RESULT_SUCCESS;
             msg.sendToTarget();
@@ -235,6 +213,34 @@
                 any(Message.class));
     }
 
+    private DataCallResponse createDataCallResponse(int cid, int linkStatus,
+            List<TrafficDescriptor> tds) {
+        return new DataCallResponse.Builder()
+                .setCause(0)
+                .setRetryDurationMillis(-1L)
+                .setId(cid)
+                .setLinkStatus(linkStatus)
+                .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(new ArrayList<>())
+                .setTrafficDescriptors(tds)
+                .build();
+    }
+
     private void setFailedSetupDataResponse(DataServiceManager dsm,
             @DataServiceCallback.ResultCode int resultCode) {
         doAnswer(invocation -> {
@@ -340,7 +346,6 @@
         doReturn(true).when(mDataConfigManager).isTempNotMeteredSupportedByCarrier();
         doReturn(true).when(mDataConfigManager).isNetworkTypeUnmetered(
                 any(TelephonyDisplayInfo.class), any(ServiceState.class));
-        doReturn(true).when(mDataConfigManager).isImsDelayTearDownEnabled();
         doReturn(DEFAULT_MTU).when(mDataConfigManager).getDefaultMtu();
         doReturn(FAKE_IMSI).when(mPhone).getSubscriberId();
         doReturn(true).when(mDataNetworkController)
@@ -474,9 +479,13 @@
 
     @Test
     public void testCreateDataNetworkWhenOos() throws Exception {
-        DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo(8, false, true, true,
-                new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_SUPPORTED,
-                        LteVopsSupportInfo.LTE_STATUS_SUPPORTED));
+        DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8)
+                .setNrAvailable(true)
+                .setEnDcAvailable(true)
+                .setVopsSupportInfo(new LteVopsSupportInfo(
+                        LteVopsSupportInfo.LTE_STATUS_SUPPORTED,
+                        LteVopsSupportInfo.LTE_STATUS_SUPPORTED))
+                .build();
         // Out of service
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
                 NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING, dsri);
@@ -516,9 +525,13 @@
     public void testRecreateAgentWhenOos() throws Exception {
         testCreateDataNetwork();
 
-        DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo(8, false, true, true,
-                new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_SUPPORTED,
-                        LteVopsSupportInfo.LTE_STATUS_SUPPORTED));
+        DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8)
+                .setNrAvailable(true)
+                .setEnDcAvailable(true)
+                .setVopsSupportInfo(new LteVopsSupportInfo(
+                        LteVopsSupportInfo.LTE_STATUS_SUPPORTED,
+                        LteVopsSupportInfo.LTE_STATUS_SUPPORTED))
+                .build();
         // Out of service
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
                 NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING, dsri);
@@ -1366,9 +1379,13 @@
 
     @Test
     public void testMovingToNonVops() throws Exception {
-        DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo(8, false, true, true,
-                new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_SUPPORTED,
-                        LteVopsSupportInfo.LTE_STATUS_SUPPORTED));
+        DataSpecificRegistrationInfo dsri = new DataSpecificRegistrationInfo.Builder(8)
+                .setNrAvailable(true)
+                .setEnDcAvailable(true)
+                .setVopsSupportInfo(new LteVopsSupportInfo(
+                        LteVopsSupportInfo.LTE_STATUS_SUPPORTED,
+                        LteVopsSupportInfo.LTE_STATUS_SUPPORTED))
+                .build();
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri);
         testCreateImsDataNetwork();
@@ -1376,9 +1393,13 @@
         assertThat(mDataNetworkUT.getNetworkCapabilities().hasCapability(
                 NetworkCapabilities.NET_CAPABILITY_MMTEL)).isTrue();
 
-        dsri = new DataSpecificRegistrationInfo(8, false, true, true,
-                new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED,
-                        LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED));
+        dsri = new DataSpecificRegistrationInfo.Builder(8)
+                .setNrAvailable(true)
+                .setEnDcAvailable(true)
+                .setVopsSupportInfo(new LteVopsSupportInfo(
+                        LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED,
+                        LteVopsSupportInfo.LTE_STATUS_NOT_SUPPORTED))
+                .build();
         logd("Trigger non VoPS");
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri);
@@ -1754,4 +1775,36 @@
         assertThat(mDataNetworkUT.getLinkProperties().getAllAddresses()).containsExactly(
                 InetAddresses.parseNumericAddress(IPV4_ADDRESS));
     }
+
+    @Test
+    public void testLinkStatusUpdate() throws Exception {
+        setupDataNetwork();
+
+        // verify link status sent on connected
+        verify(mDataNetworkCallback).onConnected(eq(mDataNetworkUT));
+        verify(mDataNetworkCallback).onLinkStatusChanged(eq(mDataNetworkUT),
+                eq(DataCallResponse.LINK_STATUS_ACTIVE));
+
+        // data state updated
+        DataCallResponse response = createDataCallResponse(123,
+                DataCallResponse.LINK_STATUS_DORMANT, Collections.emptyList());
+        mDataNetworkUT.sendMessage(8 /*EVENT_DATA_STATE_CHANGED*/, new AsyncResult(
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN, List.of(response), null));
+        processAllMessages();
+
+        // verify link status sent on data state updated
+        assertThat(mDataNetworkUT.isConnected()).isTrue();
+        verify(mDataNetworkCallback).onLinkStatusChanged(eq(mDataNetworkUT),
+                eq(DataCallResponse.LINK_STATUS_DORMANT));
+
+        // RIL crash
+        mDataNetworkUT.sendMessage(4 /*EVENT_RADIO_NOT_AVAILABLE*/);
+        processAllMessages();
+
+        // verify link status sent on disconnected
+        verify(mDataNetworkCallback).onDisconnected(eq(mDataNetworkUT),
+                eq(DataFailCause.RADIO_NOT_AVAILABLE), eq(DataNetwork.TEAR_DOWN_REASON_NONE));
+        verify(mDataNetworkCallback).onLinkStatusChanged(eq(mDataNetworkUT),
+                eq(DataCallResponse.LINK_STATUS_INACTIVE));
+    }
 }
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 0c01c90..e10c2a5 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileManagerTest.java
@@ -75,6 +75,7 @@
 @TestableLooper.RunWithLooper
 public class DataProfileManagerTest extends TelephonyTest {
     private static final String GENERAL_PURPOSE_APN = "GP_APN";
+    private static final String GENERAL_PURPOSE_APN_LEGACY_RAT = "GP_APN_RAT";
     private static final String GENERAL_PURPOSE_APN1 = "GP_APN1";
     private static final String IMS_APN = "IMS_APN";
     private static final String TETHERING_APN = "DUN_APN";
@@ -170,6 +171,41 @@
                         -1,                     // skip_464xlat
                         0                       // always_on
                 },
+                // default internet data profile for RAT CDMA, to test update preferred data profile
+                new Object[]{
+                        9,                      // id
+                        PLMN,                   // numeric
+                        GENERAL_PURPOSE_APN_LEGACY_RAT,    // name
+                        GENERAL_PURPOSE_APN_LEGACY_RAT,    // apn
+                        "",                     // proxy
+                        "",                     // port
+                        "",                     // mmsc
+                        "",                     // mmsproxy
+                        "",                     // mmsport
+                        "",                     // user
+                        "",                     // password
+                        -1,                     // authtype
+                        "default,supl,mms,ia",  // types
+                        "IPV4V6",               // protocol
+                        "IPV4V6",               // roaming_protocol
+                        1,                      // carrier_enabled
+                        0,                      // profile_id
+                        1,                      // modem_cognitive
+                        0,                      // max_conns
+                        0,                      // wait_time
+                        0,                      // max_conns_time
+                        0,                      // mtu
+                        1280,                   // mtu_v4
+                        1280,                   // mtu_v6
+                        "",                     // mvno_type
+                        "",                     // mnvo_match_data
+                        TelephonyManager.NETWORK_TYPE_BITMASK_CDMA, // network_type_bitmask
+                        0,                      // lingering_network_type_bitmask
+                        DEFAULT_APN_SET_ID,     // apn_set_id
+                        -1,                     // carrier_id
+                        -1,                     // skip_464xlat
+                        0                       // always_on
+                },
                 new Object[]{
                         2,                      // id
                         PLMN,                   // numeric
@@ -777,13 +813,51 @@
         assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo(GENERAL_PURPOSE_APN);
         dataProfile.setLastSetupTimestamp(SystemClock.elapsedRealtime());
         dataProfile.setPreferred(true);
-        mDataNetworkControllerCallback.onInternetDataNetworkConnected(List.of(dataProfile));
+        DataNetwork internetNetwork = Mockito.mock(DataNetwork.class);
+        doReturn(dataProfile).when(internetNetwork).getDataProfile();
+        doReturn(new DataNetworkController.NetworkRequestList(List.of(tnr)))
+                .when(internetNetwork).getAttachedNetworkRequestList();
+        mDataNetworkControllerCallback.onInternetDataNetworkConnected(List.of(internetNetwork));
         processAllMessages();
 
-        // See if the same one can be returned.
+        // Test See if the same one can be returned.
         dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
                 tnr, TelephonyManager.NETWORK_TYPE_LTE, false);
         assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo(GENERAL_PURPOSE_APN);
+        assertThat(mDataProfileManagerUT.isDataProfilePreferred(dataProfile)).isTrue();
+
+        // Test Another default internet network connected due to RAT changed. Verify the preferred
+        // data profile is updated.
+        DataProfile legacyRatDataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
+                tnr, TelephonyManager.NETWORK_TYPE_CDMA, false);
+        DataNetwork legacyRatInternetNetwork = Mockito.mock(DataNetwork.class);
+        doReturn(legacyRatDataProfile).when(legacyRatInternetNetwork).getDataProfile();
+        doReturn(new DataNetworkController.NetworkRequestList(List.of(tnr)))
+                .when(legacyRatInternetNetwork).getAttachedNetworkRequestList();
+        mDataNetworkControllerCallback.onInternetDataNetworkConnected(List.of(
+                // Because internetNetwork is torn down due to network type mismatch
+                legacyRatInternetNetwork));
+        processAllMessages();
+
+        assertThat(mDataProfileManagerUT.isDataProfilePreferred(legacyRatDataProfile)).isTrue();
+
+        // Test Another supl default internet network temporarily connected. Verify preferred
+        // doesn't change.
+        TelephonyNetworkRequest suplTnr = new TelephonyNetworkRequest(
+                new NetworkRequest.Builder()
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)
+                        .build(), mPhone);
+        DataProfile suplDataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
+                suplTnr, TelephonyManager.NETWORK_TYPE_LTE, false);
+        DataNetwork suplInternetNetwork = Mockito.mock(DataNetwork.class);
+        doReturn(suplDataProfile).when(suplInternetNetwork).getDataProfile();
+        doReturn(new DataNetworkController.NetworkRequestList(List.of(suplTnr)))
+                .when(suplInternetNetwork).getAttachedNetworkRequestList();
+        mDataNetworkControllerCallback.onInternetDataNetworkConnected(List.of(
+                legacyRatInternetNetwork, suplInternetNetwork));
+        processAllMessages();
+
+        assertThat(mDataProfileManagerUT.isDataProfilePreferred(legacyRatDataProfile)).isTrue();
     }
 
     @Test
@@ -1065,11 +1139,12 @@
                 tnr, TelephonyManager.NETWORK_TYPE_LTE, false);
         assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo(GENERAL_PURPOSE_APN);
         dataProfile.setLastSetupTimestamp(SystemClock.elapsedRealtime());
-        mDataNetworkControllerCallback.onInternetDataNetworkConnected(List.of(dataProfile));
+        DataNetwork internetNetwork = Mockito.mock(DataNetwork.class);
+        doReturn(dataProfile).when(internetNetwork).getDataProfile();
+        mDataNetworkControllerCallback.onInternetDataNetworkConnected(List.of(internetNetwork));
         processAllMessages();
 
         // After internet connected, preferred APN should be set
-        assertThat(mDataProfileManagerUT.isAnyPreferredDataProfileExisting()).isTrue();
         assertThat(mDataProfileManagerUT.isDataProfilePreferred(dataProfile)).isTrue();
 
         // APN reset
@@ -1078,7 +1153,6 @@
         processAllMessages();
 
         // preferred APN should set to be the last data profile that succeeded for internet setup
-        assertThat(mDataProfileManagerUT.isAnyPreferredDataProfileExisting()).isTrue();
         assertThat(mDataProfileManagerUT.isDataProfilePreferred(dataProfile)).isTrue();
 
         // Test user selected a bad data profile, expects to adopt the last data profile that
@@ -1088,7 +1162,6 @@
         mDataProfileManagerUT.obtainMessage(2 /*EVENT_APN_DATABASE_CHANGED*/).sendToTarget();
         processAllMessages();
 
-        assertThat(mDataProfileManagerUT.isAnyPreferredDataProfileExisting()).isTrue();
         assertThat(mDataProfileManagerUT.isDataProfilePreferred(dataProfile)).isFalse();
 
         // APN reset, preferred APN should set to be the last data profile that succeeded for
@@ -1097,11 +1170,10 @@
         mDataProfileManagerUT.obtainMessage(2 /*EVENT_APN_DATABASE_CHANGED*/).sendToTarget();
         processAllMessages();
 
-        assertThat(mDataProfileManagerUT.isAnyPreferredDataProfileExisting()).isTrue();
         assertThat(mDataProfileManagerUT.isDataProfilePreferred(dataProfile)).isTrue();
 
         // Test removed data profile(user created after reset) shouldn't show up
-        mDataNetworkControllerCallback.onInternetDataNetworkConnected(List.of(dataProfile));
+        mDataNetworkControllerCallback.onInternetDataNetworkConnected(List.of(internetNetwork));
         processAllMessages();
         //APN reset and removed GENERAL_PURPOSE_APN from APN DB
         mPreferredApnId = -1;
@@ -1110,7 +1182,6 @@
         processAllMessages();
 
         // There should be no preferred APN after APN reset because last working profile is removed
-        assertThat(mDataProfileManagerUT.isAnyPreferredDataProfileExisting()).isFalse();
         assertThat(mDataProfileManagerUT.isDataProfilePreferred(dataProfile)).isFalse();
 
         // restore mApnSettingContentProvider
@@ -1194,7 +1265,7 @@
     }
 
     @Test
-    public void testDataProfileCompatibility() throws Exception {
+    public void testDataProfileCompatibility() {
         DataProfile enterpriseDataProfile = new DataProfile.Builder()
                 .setTrafficDescriptor(new TrafficDescriptor(null,
                         new TrafficDescriptor.OsAppId(TrafficDescriptor.OsAppId.ANDROID_OS_ID,
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java
index 25ca7b1..84b3302 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java
@@ -24,12 +24,17 @@
 
 import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.Intent;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.os.AsyncResult;
@@ -130,6 +135,7 @@
             .build();
 
     // Mocked classes
+    private AlarmManager mAlarmManager;
     private DataRetryManagerCallback mDataRetryManagerCallbackMock;
 
     private DataRetryManager mDataRetryManagerUT;
@@ -147,6 +153,7 @@
             ((Runnable) invocation.getArguments()[0]).run();
             return null;
         }).when(mDataRetryManagerCallbackMock).invokeFromExecutor(any(Runnable.class));
+        mAlarmManager = Mockito.mock(AlarmManager.class);
         SparseArray<DataServiceManager> mockedDataServiceManagers = new SparseArray<>();
         mockedDataServiceManagers.put(AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
                 mMockedWwanDataServiceManager);
@@ -166,6 +173,8 @@
                 dataNetworkControllerCallbackCaptor.capture());
         mDataNetworkControllerCallback = dataNetworkControllerCallbackCaptor.getValue();
 
+        replaceInstance(DataRetryManager.class, "mAlarmManager",
+                mDataRetryManagerUT, mAlarmManager);
         logd("DataRetryManagerTest -Setup!");
     }
 
@@ -356,14 +365,28 @@
                 .setSetupRetryType(1)
                 .build();
         mDataRetryEntries.addAll(List.of(scheduledRetry1, scheduledRetry2));
+        // Suppose we set the data profile as permanently failed.
+        mDataProfile3.getApnSetting().setPermanentFailed(true);
+
+        DataProfile dataProfile3ReconstructedFromModem = new DataProfile.Builder()
+                .setApnSetting(new ApnSetting.Builder()
+                        .setEntryName("some_fake_ims")
+                        .setApnName("fake_ims")
+                        .setApnTypeBitmask(ApnSetting.TYPE_IMS)
+                        .setProtocol(ApnSetting.PROTOCOL_IPV6)
+                        .setRoamingProtocol(ApnSetting.PROTOCOL_IP)
+                        .build())
+                .build();
 
         // unthrottle the data profile, expect previous retries of the same transport is cancelled
         mDataRetryManagerUT.obtainMessage(6/*EVENT_DATA_PROFILE_UNTHROTTLED*/,
-                new AsyncResult(AccessNetworkConstants.TRANSPORT_TYPE_WWAN, mDataProfile3, null))
+                new AsyncResult(AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                        dataProfile3ReconstructedFromModem, null))
                 .sendToTarget();
         processAllMessages();
 
         // check unthrottle
+        assertThat(mDataProfile3.getApnSetting().getPermanentFailed()).isFalse();
         ArgumentCaptor<List<ThrottleStatus>> throttleStatusCaptor =
                 ArgumentCaptor.forClass(List.class);
         verify(mDataRetryManagerCallbackMock).onThrottleStatusChanged(
@@ -497,7 +520,6 @@
         processAllMessages();
 
         NetworkRequest request = new NetworkRequest.Builder()
-
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .build();
         TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone);
@@ -587,6 +609,16 @@
         // Verify there is no retry.
         verify(mDataRetryManagerCallbackMock, never())
                 .onDataNetworkSetupRetry(any(DataSetupRetryEntry.class));
+
+        // 4th failed on a different transport and retry.
+        mDataRetryManagerUT.evaluateDataSetupRetry(mDataProfile1,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN, networkRequestList, 123,
+                DataCallResponse.RETRY_DURATION_UNDEFINED);
+        processAllFutureMessages();
+
+        // Verify retry occurs
+        verify(mDataRetryManagerCallbackMock)
+                .onDataNetworkSetupRetry(any(DataSetupRetryEntry.class));
     }
 
     @Test
@@ -740,6 +772,43 @@
     }
 
     @Test
+    public void testDataRetryLongTimer() {
+        // Rule requires a long timer
+        DataSetupRetryRule retryRule = new DataSetupRetryRule(
+                "capabilities=internet, retry_interval=120000, maximum_retries=2");
+        doReturn(Collections.singletonList(retryRule)).when(mDataConfigManager)
+                .getDataSetupRetryRules();
+        mDataConfigManagerCallback.onCarrierConfigChanged();
+        processAllMessages();
+
+        NetworkRequest request = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .build();
+        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone);
+        DataNetworkController.NetworkRequestList
+                networkRequestList = new DataNetworkController.NetworkRequestList(tnr);
+        mDataRetryManagerUT.evaluateDataSetupRetry(mDataProfile1,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN, networkRequestList, 2253,
+                DataCallResponse.RETRY_DURATION_UNDEFINED);
+        processAllFutureMessages();
+
+        // Verify scheduled via Alarm Manager
+        ArgumentCaptor<PendingIntent> pendingIntentArgumentCaptor =
+                ArgumentCaptor.forClass(PendingIntent.class);
+        verify(mAlarmManager).setAndAllowWhileIdle(anyInt(), anyLong(),
+                pendingIntentArgumentCaptor.capture());
+
+        // Verify starts retry attempt after receiving intent
+        PendingIntent pendingIntent = pendingIntentArgumentCaptor.getValue();
+        Intent intent = pendingIntent.getIntent();
+        mContext.sendBroadcast(intent);
+        processAllFutureMessages();
+
+        verify(mDataRetryManagerCallbackMock)
+                .onDataNetworkSetupRetry(any(DataSetupRetryEntry.class));
+    }
+
+    @Test
     public void testDataHandoverRetryInvalidRulesFromString() {
         assertThrows(IllegalArgumentException.class,
                 () -> new DataHandoverRetryRule("V2hhdCBUaGUgRnVjayBpcyB0aGlzIQ=="));
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 7a1aeca..f7525c1 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java
@@ -22,7 +22,6 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -64,9 +63,6 @@
         mBundle = mContextFixture.getCarrierConfigBundle();
         doReturn(true).when(mDataConfigManager).isConfigCarrierSpecific();
 
-        doReturn("").when(mSubscriptionController).getEnabledMobileDataPolicies(anyInt());
-        doReturn(true).when(mSubscriptionController).setEnabledMobileDataPolicies(
-                anyInt(), anyString());
         doReturn(new SubscriptionInfoInternal.Builder().setId(1).build())
                 .when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
 
@@ -108,13 +104,8 @@
         processAllMessages();
 
         ArgumentCaptor<String> stringArgumentCaptor = ArgumentCaptor.forClass(String.class);
-        if (isSubscriptionManagerServiceEnabled()) {
-            verify(mSubscriptionManagerService, times(2))
-                    .setEnabledMobileDataPolicies(anyInt(), stringArgumentCaptor.capture());
-        } else {
-            verify(mSubscriptionController, times(2))
-                    .setEnabledMobileDataPolicies(anyInt(), stringArgumentCaptor.capture());
-        }
+        verify(mSubscriptionManagerService, times(2))
+                .setEnabledMobileDataPolicies(anyInt(), stringArgumentCaptor.capture());
         assertEquals("1,2", stringArgumentCaptor.getValue());
     }
 
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 5bf66c5..35d3b92 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataStallRecoveryManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataStallRecoveryManagerTest.java
@@ -19,7 +19,6 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -29,7 +28,6 @@
 import android.net.NetworkAgent;
 import android.telephony.Annotation.ValidationStatus;
 import android.telephony.CarrierConfigManager;
-import android.telephony.data.DataProfile;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -71,14 +69,10 @@
                 .getDataStallRecoveryShouldSkipArray();
         doReturn(true).when(mDataNetworkController).isInternetDataAllowed();
 
-        doAnswer(
-                invocation -> {
-                    ((Runnable) invocation.getArguments()[0]).run();
-                    return null;
-                })
-                .when(mDataStallRecoveryManagerCallback)
-                .invokeFromExecutor(any(Runnable.class));
-        doReturn("").when(mSubscriptionController).getEnabledMobileDataPolicies(anyInt());
+        doAnswer(invocation -> {
+            ((Runnable) invocation.getArguments()[0]).run();
+            return null;
+        }).when(mDataStallRecoveryManagerCallback).invokeFromExecutor(any(Runnable.class));
 
         mDataStallRecoveryManager =
                 new DataStallRecoveryManager(
@@ -117,7 +111,7 @@
                 dataNetworkControllerCallbackCaptor.getValue();
 
         if (isConnected) {
-            List<DataProfile> dataprofile = new ArrayList<DataProfile>();
+            List<DataNetwork> dataprofile = new ArrayList<>();
             dataNetworkControllerCallback.onInternetDataNetworkConnected(dataprofile);
         } else {
             dataNetworkControllerCallback.onInternetDataNetworkDisconnected();
@@ -352,54 +346,6 @@
     }
 
     @Test
-    public void testNextRecoveryAfterSkippingUnderPoorSignal() throws Exception {
-        // Test to validate if the next recovery action is performed in good signal
-        // soon after skipping the recovery action under poor signal condition
-        sendOnInternetDataNetworkCallback(true);
-        mDataStallRecoveryManager.setRecoveryAction(1);
-        doReturn(1).when(mSignalStrength).getLevel();
-        doReturn(mSignalStrength).when(mPhone).getSignalStrength();
-        doReturn(PhoneConstants.State.IDLE).when(mPhone).getState();
-
-        logd("Sending validation failed callback");
-        sendValidationStatusCallback(NetworkAgent.VALIDATION_STATUS_NOT_VALID);
-        processAllMessages();
-        moveTimeForward(101);
-
-        // verify skipping recovery action under poor signal condition
-        assertThat(mDataStallRecoveryManager.getRecoveryAction()).isEqualTo(1);
-
-        // Set the signal condition to good
-        doReturn(3).when(mSignalStrength).getLevel();
-
-        logd("Sending validation failed callback");
-        sendValidationStatusCallback(NetworkAgent.VALIDATION_STATUS_NOT_VALID);
-        processAllMessages();
-        moveTimeForward(101);
-
-        // verify next recovery action is performed under good signal condition
-        assertThat(mDataStallRecoveryManager.getRecoveryAction()).isEqualTo(3);
-    }
-
-    @Test
-    public void testDoNotRecoveryForAlwaysInvalidNetwork() throws Exception {
-        // Test to verify that recovery action is not performed for always invalid network
-        // In some lab testing scenarios, n/w validation always remain invalid.
-        sendOnInternetDataNetworkCallback(false);
-        doReturn(mSignalStrength).when(mPhone).getSignalStrength();
-        doReturn(PhoneConstants.State.IDLE).when(mPhone).getState();
-        mDataStallRecoveryManager
-                .setRecoveryAction(DataStallRecoveryManager.RECOVERY_ACTION_GET_DATA_CALL_LIST);
-
-        logd("Sending validation failed callback");
-        sendValidationStatusCallback(NetworkAgent.VALIDATION_STATUS_NOT_VALID);
-        processAllFutureMessages();
-        moveTimeForward(101);
-
-        assertThat(mDataStallRecoveryManager.getRecoveryAction()).isEqualTo(0);
-    }
-
-    @Test
     public void testStartTimeNotZero() throws Exception {
         sendOnInternetDataNetworkCallback(false);
         doReturn(mSignalStrength).when(mPhone).getSignalStrength();
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 f0a73a9..bc691f6 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java
@@ -199,7 +199,7 @@
         initialize();
 
         // verify nothing has been done while there are no inputs
-        assertFalse("data allowed initially", mDataAllowed[0]);
+        assertTrue("data should be always allowed for emergency", mDataAllowed[0]);
         assertFalse("data allowed initially", mDataAllowed[1]);
 
         NetworkRequest internetNetworkRequest = addInternetNetworkRequest(null, 50);
@@ -305,7 +305,7 @@
         processAllMessages();
         verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
         clearInvocations(mActivePhoneSwitchHandler);
-        assertFalse("data allowed", mDataAllowed[0]);
+        assertTrue("data not allowed", mDataAllowed[0]);
         assertFalse("data allowed", mDataAllowed[1]);
 
         // 6 gain subscription-specific request
@@ -349,7 +349,7 @@
         processAllMessages();
         verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
         clearInvocations(mActivePhoneSwitchHandler);
-        assertFalse("data allowed", mDataAllowed[0]);
+        assertTrue("data not allowed", mDataAllowed[0]);
         assertFalse("data allowed", mDataAllowed[1]);
 
         // 10 don't switch phones when in emergency mode
@@ -562,7 +562,7 @@
 
     @Test
     @SmallTest
-    public void testAutoDataSwitchRetry() throws Exception {
+    public void testAutoDataSwitch_retry() throws Exception {
         initialize();
         // Phone 0 has sub 1, phone 1 has sub 2.
         // Sub 1 is default data sub.
@@ -590,10 +590,9 @@
 
     @Test
     @SmallTest
-    public void testAutoDataSwitchSetNotification() throws Exception {
+    public void testAutoDataSwitch_setNotification() throws Exception {
         SubscriptionInfo mockedInfo = mock(SubscriptionInfo.class);
         doReturn(false).when(mockedInfo).isOpportunistic();
-        doReturn(mockedInfo).when(mSubscriptionController).getSubscriptionInfo(anyInt());
         doReturn(mockedInfo).when(mSubscriptionManagerService).getSubscriptionInfo(anyInt());
         initialize();
         // Phone 0 has sub 1, phone 1 has sub 2.
@@ -603,37 +602,59 @@
         setDefaultDataSubId(1);
 
         testAutoSwitchToSecondarySucceed();
-        clearInvocations(mSubscriptionController);
         clearInvocations(mSubscriptionManagerService);
         Message.obtain(mPhoneSwitcherUT, EVENT_MODEM_COMMAND_DONE, new AsyncResult(1, null,  null))
                 .sendToTarget();
         processAllMessages();
-        if (isSubscriptionManagerServiceEnabled()) {
-            verify(mSubscriptionManagerService).getSubscriptionInfo(2);
-        } else {
-            verify(mSubscriptionController).getSubscriptionInfo(2);
-        }
-
+        verify(mSubscriptionManagerService).getSubscriptionInfo(2);
         // switch back to primary
-        clearInvocations(mSubscriptionController);
         clearInvocations(mSubscriptionManagerService);
         Message.obtain(mPhoneSwitcherUT, EVENT_MODEM_COMMAND_DONE, new AsyncResult(0, null,  null))
                 .sendToTarget();
         processAllMessages();
-        if (isSubscriptionManagerServiceEnabled()) {
-            verify(mSubscriptionManagerService, never()).getSubscriptionInfo(1);
-        } else {
-            verify(mSubscriptionController, never()).getSubscriptionInfo(1);
-        }
+        verify(mSubscriptionManagerService, never()).getSubscriptionInfo(1);
 
         Message.obtain(mPhoneSwitcherUT, EVENT_MODEM_COMMAND_DONE, new AsyncResult(1, null,  null))
                 .sendToTarget();
         processAllMessages();
-        if (isSubscriptionManagerServiceEnabled()) {
-            verify(mSubscriptionManagerService, never()).getSubscriptionInfo(2);
-        } else {
-            verify(mSubscriptionController, never()).getSubscriptionInfo(2);
-        }
+        verify(mSubscriptionManagerService, never()).getSubscriptionInfo(2);
+    }
+
+    @Test
+    @SmallTest
+    public void testAutoDataSwitch_exemptPingTest() throws Exception {
+        initialize();
+        // Change resource overlay
+        doReturn(false).when(mDataConfigManager).isPingTestBeforeAutoDataSwitchRequired();
+        mPhoneSwitcherUT = new PhoneSwitcher(mMaxDataAttachModemCount, mContext, Looper.myLooper());
+        processAllMessages();
+
+        // Phone 0 has sub 1, phone 1 has sub 2.
+        // Sub 1 is default data sub.
+        setSlotIndexToSubId(0, 1);
+        setSlotIndexToSubId(1, 2);
+        setDefaultDataSubId(1);
+
+        //1. Attempting to switch to nDDS, switch even if validation failed
+        prepareIdealAutoSwitchCondition();
+        processAllFutureMessages();
+
+        verify(mCellularNetworkValidator).validate(eq(2), anyLong(), eq(false),
+                eq(mPhoneSwitcherUT.mValidationCallback));
+        mPhoneSwitcherUT.mValidationCallback.onValidationDone(false, 2);
+        processAllMessages();
+
+        assertEquals(2, mPhoneSwitcherUT.getActiveDataSubId()); // switch succeeds
+
+        //2. Attempting to switch back to DDS, switch even if validation failed
+        serviceStateChanged(0, NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
+        processAllFutureMessages();
+        verify(mCellularNetworkValidator).validate(eq(1), anyLong(), eq(false),
+                eq(mPhoneSwitcherUT.mValidationCallback));
+        mPhoneSwitcherUT.mValidationCallback.onValidationDone(false, 1);
+        processAllMessages();
+
+        assertEquals(1, mPhoneSwitcherUT.getActiveDataSubId()); // switch succeeds
     }
 
     /**
@@ -732,7 +753,6 @@
         setSlotIndexToSubId(1, 2);
         setDefaultDataSubId(1);
 
-        doReturn(true).when(mSubscriptionController).isOpportunistic(2);
         doReturn(new SubscriptionInfoInternal.Builder(mSubscriptionManagerService
                 .getSubscriptionInfoInternal(2)).setOpportunistic(1).build())
                 .when(mSubscriptionManagerService).getSubscriptionInfoInternal(2);
@@ -779,7 +799,6 @@
         setSlotIndexToSubId(0, 1);
         setSlotIndexToSubId(1, 2);
         // single visible sub, as the other one is CBRS
-        doReturn(new int[1]).when(mSubscriptionController).getActiveSubIdList(true);
         doReturn(new int[1]).when(mSubscriptionManagerService).getActiveSubIdList(true);
         setDefaultDataSubId(1);
 
@@ -836,7 +855,6 @@
         setDefaultDataSubId(1);
 
         clearInvocations(mCellularNetworkValidator);
-        doReturn(new int[1]).when(mSubscriptionController).getActiveSubIdList(true);
         doReturn(new int[1]).when(mSubscriptionManagerService).getActiveSubIdList(true);
         prepareIdealAutoSwitchCondition();
         processAllFutureMessages();
@@ -890,7 +908,6 @@
                 new TelephonyNetworkRequest(mmsRequest, mPhone), 1));
 
         // Set sub 2 as preferred sub should make phone 1 preferredDataModem
-        doReturn(true).when(mSubscriptionController).isOpportunistic(2);
         doReturn(new SubscriptionInfoInternal.Builder(mSubscriptionManagerService
                 .getSubscriptionInfoInternal(2)).setOpportunistic(1).build())
                 .when(mSubscriptionManagerService).getSubscriptionInfoInternal(2);
@@ -952,8 +969,6 @@
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
         initialize();
 
-        // Mark sub 2 as opportunistic.
-        doReturn(true).when(mSubscriptionController).isOpportunistic(2);
         // Phone 0 has sub 1, phone 1 has sub 2.
         // Sub 1 is default data sub.
         // Both are active subscriptions are active sub, as they are in both active slots.
@@ -1514,8 +1529,6 @@
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
         initialize();
 
-        // Mark sub 2 as opportunistic.
-        doReturn(true).when(mSubscriptionController).isOpportunistic(2);
         // Phone 0 has sub 1, phone 1 has sub 2.
         // Sub 1 is default data sub.
         // Both are active subscriptions are active sub, as they are in both active slots.
@@ -1677,7 +1690,6 @@
         setAllPhonesInactive();
         // Initialization done.
 
-        doReturn(true).when(mSubscriptionController).isOpportunistic(2);
         doReturn(new SubscriptionInfoInternal.Builder(mSubscriptionManagerService
                 .getSubscriptionInfoInternal(2)).setOpportunistic(1).build())
                 .when(mSubscriptionManagerService).getSubscriptionInfoInternal(2);
@@ -1719,7 +1731,6 @@
         setAllPhonesInactive();
         // Initialization done.
 
-        doReturn(true).when(mSubscriptionController).isOpportunistic(2);
         doReturn(new SubscriptionInfoInternal.Builder(mSubscriptionManagerService
                 .getSubscriptionInfoInternal(2)).setOpportunistic(1).build())
                 .when(mSubscriptionManagerService).getSubscriptionInfoInternal(2);
@@ -1770,8 +1781,6 @@
         verify(mMockRadioConfig, times(1)).setPreferredDataModem(eq(0), any());
 
         clearInvocations(mMockRadioConfig);
-        doReturn(mSubscriptionInfo).when(mSubscriptionController)
-            .getActiveSubscriptionInfoForSimSlotIndex(eq(0), any(), any());
         doReturn(mSubscriptionInfo).when(mSubscriptionManagerService)
                 .getActiveSubscriptionInfoForSimSlotIndex(eq(0), any(), any());
         doReturn(true).when(mSubscriptionInfo).areUiccApplicationsEnabled();
@@ -1795,6 +1804,36 @@
         verify(mMockRadioConfig, times(1)).setPreferredDataModem(eq(0), any());
     }
 
+    @Test
+    public void testScheduledRetryWhileMultiSimConfigChange() throws Exception {
+        doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
+        initialize();
+
+        // Phone 0 has sub 1, phone 1 has sub 2.
+        // Sub 1 is default data sub.
+        setSlotIndexToSubId(0, 1);
+        setSlotIndexToSubId(1, 2);
+
+        // for EVENT_MODEM_COMMAND_RETRY
+        AsyncResult res = new AsyncResult(
+                1, null,  new CommandException(CommandException.Error.GENERIC_FAILURE));
+        Message.obtain(mPhoneSwitcherUT, EVENT_MODEM_COMMAND_DONE, res).sendToTarget();
+        processAllMessages();
+
+        // reduce count of phone
+        setNumPhones(1, 1);
+        AsyncResult result = new AsyncResult(null, 1, null);
+        Message.obtain(mPhoneSwitcherUT, EVENT_MULTI_SIM_CONFIG_CHANGED, result).sendToTarget();
+        processAllMessages();
+
+        // fire retries
+        moveTimeForward(5000);
+        processAllMessages();
+
+        verify(mCommandsInterface0, never()).setDataAllowed(anyBoolean(), any());
+        verify(mCommandsInterface1, never()).setDataAllowed(anyBoolean(), any());
+    }
+
     /* Private utility methods start here */
 
     private void prepareIdealAutoSwitchCondition() {
@@ -1962,9 +2001,9 @@
         initializeCommandInterfacesMock();
         initializeTelRegistryMock();
         initializeConnManagerMock();
+        initializeConfigMock();
 
         mPhoneSwitcherUT = new PhoneSwitcher(mMaxDataAttachModemCount, mContext, Looper.myLooper());
-        processAllMessages();
 
         Field field = PhoneSwitcher.class.getDeclaredField("mDataSettingsManagerCallbacks");
         field.setAccessible(true);
@@ -1972,11 +2011,7 @@
                 (Map<Integer, DataSettingsManager.DataSettingsManagerCallback>)
                         field.get(mPhoneSwitcherUT);
 
-        int deviceConfigValue = 10000;
-        field = PhoneSwitcher.class.getDeclaredField(
-                "mAutoDataSwitchAvailabilityStabilityTimeThreshold");
-        field.setAccessible(true);
-        field.setInt(mPhoneSwitcherUT, deviceConfigValue);
+        processAllMessages();
 
         verify(mTelephonyRegistryManager).addOnSubscriptionsChangedListener(any(), any());
     }
@@ -2068,25 +2103,12 @@
      * network requests on PhoneSwitcher.
      */
     private void initializeSubControllerMock() throws Exception {
-        doReturn(mDefaultDataSub).when(mSubscriptionController).getDefaultDataSubId();
         doReturn(mDefaultDataSub).when(mSubscriptionManagerService).getDefaultDataSubId();
         doReturn(mDefaultDataSub).when(mMockedIsub).getDefaultDataSubId();
-        doReturn(0).when(mSubscriptionController).getPhoneId(1);
         doReturn(0).when(mSubscriptionManagerService).getPhoneId(1);
         doReturn(0).when(mMockedIsub).getPhoneId(1);
-        doReturn(1).when(mSubscriptionController).getPhoneId(2);
         doReturn(1).when(mSubscriptionManagerService).getPhoneId(2);
         doReturn(1).when(mMockedIsub).getPhoneId(2);
-        doAnswer(invocation -> {
-            int phoneId = (int) invocation.getArguments()[0];
-            if (phoneId == SubscriptionManager.INVALID_PHONE_INDEX) {
-                return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-            } else if (phoneId == SubscriptionManager.DEFAULT_PHONE_INDEX) {
-                return mSlotIndexToSubId[0][0];
-            } else {
-                return mSlotIndexToSubId[phoneId][0];
-            }
-        }).when(mSubscriptionController).getSubId(anyInt());
 
         doAnswer(invocation -> {
             int phoneId = (int) invocation.getArguments()[0];
@@ -2113,17 +2135,6 @@
         doAnswer(invocation -> {
             int subId = (int) invocation.getArguments()[0];
 
-            if (!SubscriptionManager.isUsableSubIdValue(subId)) return false;
-
-            for (int i = 0; i < mSlotIndexToSubId.length; i++) {
-                if (mSlotIndexToSubId[i][0] == subId) return true;
-            }
-            return false;
-        }).when(mSubscriptionController).isActiveSubId(anyInt());
-
-        doAnswer(invocation -> {
-            int subId = (int) invocation.getArguments()[0];
-
             if (!SubscriptionManager.isUsableSubIdValue(subId)) return null;
 
             int slotIndex = -1;
@@ -2134,15 +2145,21 @@
                     .setSimSlotIndex(slotIndex).setId(subId).build();
         }).when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
 
-        doReturn(new int[mSlotIndexToSubId.length]).when(mSubscriptionController)
-                .getActiveSubIdList(true);
         doReturn(new int[mSlotIndexToSubId.length]).when(mSubscriptionManagerService)
                 .getActiveSubIdList(true);
     }
 
+    private void initializeConfigMock() {
+        doReturn(mDataNetworkController).when(mPhone).getDataNetworkController();
+        doReturn(mDataConfigManager).when(mDataNetworkController).getDataConfigManager();
+        doReturn(1000L).when(mDataConfigManager)
+                .getAutoDataSwitchAvailabilityStabilityTimeThreshold();
+        doReturn(7).when(mDataConfigManager).getAutoDataSwitchValidationMaxRetry();
+        doReturn(true).when(mDataConfigManager).isPingTestBeforeAutoDataSwitchRequired();
+    }
+
     private void setDefaultDataSubId(int defaultDataSub) throws Exception {
         mDefaultDataSub = defaultDataSub;
-        doReturn(mDefaultDataSub).when(mSubscriptionController).getDefaultDataSubId();
         doReturn(mDefaultDataSub).when(mSubscriptionManagerService).getDefaultDataSubId();
         if (defaultDataSub == 1) {
             doReturn(true).when(mPhone).isUserDataEnabled();
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 a99518d..5941f06 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkFactoryTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkFactoryTest.java
@@ -49,7 +49,6 @@
 
 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;
@@ -213,8 +212,7 @@
         // test at this time.
         doAnswer(invocation -> {
             final NetworkCapabilities capabilitiesFilter =
-                    mTelephonyNetworkFactoryUT.makeNetworkFilter(
-                            mSubscriptionController.getSubId(0));
+                    mTelephonyNetworkFactoryUT.makeNetworkFilter(mMockedIsub.getSubId(0));
             for (final TelephonyNetworkRequest request : mAllNetworkRequestSet) {
                 final int message = request.canBeSatisfiedBy(capabilitiesFilter)
                         ? CMD_REQUEST_NETWORK : CMD_CANCEL_REQUEST;
@@ -239,7 +237,6 @@
         createMockedTelephonyComponents();
 
         doReturn(false).when(mPhoneSwitcher).shouldApplyNetworkRequest(any(), anyInt());
-        doReturn(subId).when(mSubscriptionController).getSubId(phoneId);
         doReturn(subId).when(mMockedIsub).getSubId(phoneId);
         // fake onSubscriptionChangedListener being triggered.
         mTelephonyNetworkFactoryUT.mInternalHandler.sendEmptyMessage(
@@ -302,7 +299,6 @@
      */
     @Test
     @SmallTest
-    @Ignore("b/256052233")
     public void testRequests() throws Exception {
         mTestName = "testActive";
         final int numberOfPhones = 2;
@@ -314,7 +310,6 @@
 
         createMockedTelephonyComponents();
 
-        doReturn(subId).when(mSubscriptionController).getSubId(phoneId);
         doReturn(subId).when(mMockedIsub).getSubId(phoneId);
         mTelephonyNetworkFactoryUT.mInternalHandler.sendEmptyMessage(
                 TelephonyNetworkFactory.EVENT_SUBSCRIPTION_CHANGED);
@@ -329,7 +324,6 @@
         processAllMessages();
         assertEquals(1, mNetworkRequestList.size());
 
-        doReturn(altSubId).when(mSubscriptionController).getSubId(altPhoneId);
         doReturn(altSubId).when(mMockedIsub).getSubId(altPhoneId);
         processAllMessages();
         assertEquals(1, mNetworkRequestList.size());
@@ -345,7 +339,6 @@
         processAllMessages();
         assertEquals(1, mNetworkRequestList.size());
 
-        doReturn(unusedSubId).when(mSubscriptionController).getSubId(phoneId);
         doReturn(unusedSubId).when(mMockedIsub).getSubId(phoneId);
         mTelephonyNetworkFactoryUT.mInternalHandler.sendEmptyMessage(
                 TelephonyNetworkFactory.EVENT_SUBSCRIPTION_CHANGED);
@@ -356,7 +349,6 @@
         processAllMessages();
         assertEquals(0, mNetworkRequestList.size());
 
-        doReturn(subId).when(mSubscriptionController).getSubId(phoneId);
         doReturn(subId).when(mMockedIsub).getSubId(phoneId);
         mTelephonyNetworkFactoryUT.mInternalHandler.sendEmptyMessage(
                 TelephonyNetworkFactory.EVENT_SUBSCRIPTION_CHANGED);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionConnectionTest.java
new file mode 100644
index 0000000..ce59cc6
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionConnectionTest.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2022 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 android.telephony.AccessNetworkConstants.AccessNetworkType.EUTRAN;
+import static android.telephony.AccessNetworkConstants.AccessNetworkType.UTRAN;
+import static android.telephony.DomainSelectionService.SCAN_TYPE_NO_PREFERENCE;
+import static android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.os.AsyncResult;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.telephony.DomainSelectionService;
+import android.telephony.DomainSelector;
+import android.telephony.EmergencyRegResult;
+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 com.android.internal.telephony.CallFailCause;
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+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
+public class DomainSelectionConnectionTest extends TelephonyTest {
+
+    private static final String TELECOM_CALL_ID1 = "TC1";
+
+    private DomainSelectionController mDomainSelectionController;
+    private DomainSelectionConnection.DomainSelectionConnectionCallback mConnectionCallback;
+    private DomainSelectionConnection mDsc;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(this.getClass().getSimpleName());
+
+        mDomainSelectionController = Mockito.mock(DomainSelectionController.class);
+        mConnectionCallback =
+                Mockito.mock(DomainSelectionConnection.DomainSelectionConnectionCallback.class);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mDsc = null;
+        super.tearDown();
+    }
+
+    @Test
+    @SmallTest
+    public void testTransportSelectorCallback() {
+        mDsc = new DomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+                mDomainSelectionController);
+
+        TransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+
+        assertNotNull(transportCallback);
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectDomain() {
+        mDsc = new DomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+                mDomainSelectionController);
+
+        TransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+
+        DomainSelectionService.SelectionAttributes attr = getSelectionAttributes(
+                mPhone.getPhoneId(), mPhone.getSubId(), SELECTOR_TYPE_CALLING, true,
+                false, 0, null, 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);
+    }
+
+    @Test
+    @SmallTest
+    public void testWwanSelectorCallbackAsync() throws Exception {
+        mDsc = new DomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+                mDomainSelectionController);
+        replaceInstance(DomainSelectionConnection.class, "mWwanSelectedExecutor",
+                mDsc, new Executor() {
+                    public void execute(Runnable command) {
+                        command.run();
+                    }
+                });
+
+        TransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+
+        assertNotNull(transportCallback);
+
+        Consumer<WwanSelectorCallback> consumer = Mockito.mock(Consumer.class);
+        transportCallback.onWwanSelected(consumer);
+
+        verify(consumer).accept(any());
+    }
+
+    @Test
+    @SmallTest
+    public void testWwanSelectorCallbackOnRequestEmergencyNetworkScan() throws Exception {
+        mDsc = new DomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+                mDomainSelectionController);
+
+        TransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+
+        assertNotNull(transportCallback);
+
+        WwanSelectorCallback wwanCallback = transportCallback.onWwanSelected();
+
+        assertNotNull(wwanCallback);
+
+        replaceInstance(DomainSelectionConnection.class, "mLooper",
+                mDsc, mTestableLooper.getLooper());
+        List<Integer> preferredNetworks = new ArrayList<>();
+        preferredNetworks.add(EUTRAN);
+        preferredNetworks.add(UTRAN);
+        int scanType = SCAN_TYPE_NO_PREFERENCE;
+        Consumer<EmergencyRegResult> consumer = Mockito.mock(Consumer.class);
+
+        wwanCallback.onRequestEmergencyNetworkScan(preferredNetworks, scanType, null, consumer);
+
+        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);
+
+        doReturn(new Executor() {
+            public void execute(Runnable r) {
+                r.run();
+            }
+        }).when(mDomainSelectionController).getDomainSelectionServiceExecutor();
+        EmergencyRegResult regResult =
+                new EmergencyRegResult(UTRAN, 0, 0, true, false, 0, 0, "", "", "");
+        handler.sendMessage(handler.obtainMessage(event, new AsyncResult(null, regResult, null)));
+        processAllMessages();
+
+        verify(consumer).accept(eq(regResult));
+    }
+
+    @Test
+    @SmallTest
+    public void testWwanSelectorCallbackOnRequestEmergencyNetworkScanAndCancel() throws Exception {
+        mDsc = new DomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+                mDomainSelectionController);
+
+        TransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+
+        assertNotNull(transportCallback);
+
+        WwanSelectorCallback wwanCallback = transportCallback.onWwanSelected();
+
+        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));
+
+        verify(mPhone).registerForEmergencyNetworkScan(any(), anyInt(), any());
+        verify(mPhone).triggerEmergencyNetworkScan(any(), anyInt(), any());
+
+        signal.cancel();
+
+        verify(mPhone).cancelEmergencyNetworkScan(eq(false), any());
+    }
+
+    @Test
+    @SmallTest
+    public void testDomainSelectorCancelSelection() throws Exception {
+        mDsc = new DomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+                mDomainSelectionController);
+
+        TransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+
+        assertNotNull(transportCallback);
+
+        DomainSelector domainSelector = Mockito.mock(DomainSelector.class);
+        transportCallback.onCreated(domainSelector);
+
+        mDsc.cancelSelection();
+
+        verify(domainSelector).cancelSelection();
+    }
+
+    @Test
+    @SmallTest
+    public void testDomainSelectorReselectDomain() throws Exception {
+        mDsc = new DomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+                mDomainSelectionController);
+
+        TransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+
+        assertNotNull(transportCallback);
+
+        DomainSelector domainSelector = Mockito.mock(DomainSelector.class);
+        transportCallback.onCreated(domainSelector);
+
+        DomainSelectionService.SelectionAttributes 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(any());
+    }
+
+    @Test
+    @SmallTest
+    public void testDomainSelectorFinishSelection() throws Exception {
+        mDsc = new DomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+                mDomainSelectionController);
+
+        TransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+
+        assertNotNull(transportCallback);
+
+        DomainSelector domainSelector = Mockito.mock(DomainSelector.class);
+        transportCallback.onCreated(domainSelector);
+
+        mDsc.finishSelection();
+
+        verify(domainSelector).finishSelection();
+    }
+
+    private DomainSelectionService.SelectionAttributes getSelectionAttributes(
+            int slotId, int subId, int selectorType, boolean isEmergency,
+            boolean exited, int callFailCause, String callId, String number,
+            ImsReasonInfo imsReasonInfo, EmergencyRegResult regResult) {
+        DomainSelectionService.SelectionAttributes.Builder builder =
+                new DomainSelectionService.SelectionAttributes.Builder(
+                        slotId, subId, selectorType)
+                .setEmergency(isEmergency)
+                .setExitedFromAirplaneMode(exited)
+                .setCsDisconnectCause(callFailCause);
+
+        if (callId != null) builder.setCallId(callId);
+        if (number != null) builder.setNumber(number);
+        if (imsReasonInfo != null) builder.setPsDisconnectCause(imsReasonInfo);
+        if (regResult != null) builder.setEmergencyRegResult(regResult);
+
+        return builder.build();
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionResolverTest.java b/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionResolverTest.java
new file mode 100644
index 0000000..2c65b50
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionResolverTest.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2022 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 android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING;
+import static android.telephony.TelephonyManager.HAL_SERVICE_NETWORK;
+
+import static com.android.internal.telephony.RIL.RADIO_HAL_VERSION_2_0;
+import static com.android.internal.telephony.RIL.RADIO_HAL_VERSION_2_1;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.telephony.DomainSelectionService;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.telephony.HalVersion;
+import com.android.internal.telephony.Phone;
+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;
+
+/**
+ * Unit tests for DomainSelectionResolver.
+ */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class DomainSelectionResolverTest extends TelephonyTest {
+    // Mock classes
+    private DomainSelectionController mDsController;
+    private DomainSelectionConnection mDsConnection;
+    private DomainSelectionService mDsService;
+
+    private DomainSelectionResolver mDsResolver;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+
+        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();
+    }
+
+    @Test
+    @SmallTest
+    public void testGetInstance() throws IllegalStateException {
+        assertThrows(IllegalStateException.class, () -> {
+            DomainSelectionResolver.getInstance();
+        });
+
+        DomainSelectionResolver.make(mContext, true);
+        DomainSelectionResolver resolver = DomainSelectionResolver.getInstance();
+
+        assertNotNull(resolver);
+    }
+
+    @Test
+    @SmallTest
+    public void testIsDomainSelectionSupportedWhenDeviceConfigDisabled() {
+        setUpResolver(false, RADIO_HAL_VERSION_2_1);
+
+        assertFalse(mDsResolver.isDomainSelectionSupported());
+    }
+
+    @Test
+    @SmallTest
+    public void testIsDomainSelectionSupportedWhenHalVersionLessThan20() {
+        setUpResolver(true, RADIO_HAL_VERSION_2_0);
+
+        assertFalse(mDsResolver.isDomainSelectionSupported());
+    }
+
+    @Test
+    @SmallTest
+    public void testIsDomainSelectionSupported() {
+        setUpResolver(true, RADIO_HAL_VERSION_2_1);
+
+        assertTrue(mDsResolver.isDomainSelectionSupported());
+    }
+
+    @Test
+    @SmallTest
+    public void testGetDomainSelectionConnectionWhenNotInitialized() throws Exception {
+        setUpResolver(true, RADIO_HAL_VERSION_2_1);
+
+        assertThrows(IllegalStateException.class, () -> {
+            mDsResolver.getDomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true);
+        });
+    }
+
+    @Test
+    @SmallTest
+    public void testGetDomainSelectionConnectionWhenPhoneNull() throws Exception {
+        setUpResolver(true, RADIO_HAL_VERSION_2_1);
+        mDsResolver.initialize(mDsService);
+        assertNull(mDsResolver.getDomainSelectionConnection(null, SELECTOR_TYPE_CALLING, true));
+    }
+
+    @Test
+    @SmallTest
+    public void testGetDomainSelectionConnectionWhenImsNotAvailable() throws Exception {
+        setUpResolver(true, RADIO_HAL_VERSION_2_1);
+        mDsResolver.initialize(mDsService);
+        when(mPhone.isImsAvailable()).thenReturn(false);
+
+        assertNull(mDsResolver.getDomainSelectionConnection(mPhone, SELECTOR_TYPE_CALLING, true));
+    }
+
+    @Test
+    @SmallTest
+    public void testGetDomainSelectionConnection() throws Exception {
+        setUpResolver(true, RADIO_HAL_VERSION_2_1);
+        setUpController();
+        mDsResolver.initialize(mDsService);
+        when(mPhone.isImsAvailable()).thenReturn(true);
+
+        assertNotNull(mDsResolver.getDomainSelectionConnection(
+                mPhone, SELECTOR_TYPE_CALLING, true));
+    }
+
+    private void setUpResolver(boolean deviceConfigEnabled, HalVersion halVersion) {
+        mDsResolver = new DomainSelectionResolver(mContext, deviceConfigEnabled);
+        when(mPhone.getHalVersion(eq(HAL_SERVICE_NETWORK))).thenReturn(halVersion);
+    }
+
+    private void setUpController() {
+        mDsResolver.setDomainSelectionControllerFactory(
+                new DomainSelectionResolver.DomainSelectionControllerFactory() {
+                    @Override
+                    public DomainSelectionController create(Context context,
+                            DomainSelectionService service) {
+                        return mDsController;
+                    }
+                });
+
+        when(mDsController.getDomainSelectionConnection(any(Phone.class), anyInt(), anyBoolean()))
+                .thenReturn(mDsConnection);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnectionTest.java
new file mode 100644
index 0000000..0c64b82
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnectionTest.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2022 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 android.telephony.AccessNetworkConstants.AccessNetworkType.EUTRAN;
+import static android.telephony.AccessNetworkConstants.AccessNetworkType.UTRAN;
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WLAN;
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+import static android.telephony.DisconnectCause.ERROR_UNSPECIFIED;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_CS;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN;
+
+import static com.android.internal.telephony.PhoneConstants.DOMAIN_NON_3GPP_PS;
+import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WLAN;
+import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WWAN;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.telephony.DomainSelectionService;
+import android.telephony.EmergencyRegResult;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.TransportSelectorCallback;
+import android.telephony.WwanSelectorCallback;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.data.AccessNetworksManager;
+import com.android.internal.telephony.emergency.EmergencyStateTracker;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+import java.util.concurrent.CompletableFuture;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class EmergencyCallDomainSelectionConnectionTest extends TelephonyTest {
+
+    private static final String TELECOM_CALL_ID1 = "TC1";
+
+    private DomainSelectionController mDomainSelectionController;
+    private DomainSelectionConnection.DomainSelectionConnectionCallback mConnectionCallback;
+    private EmergencyCallDomainSelectionConnection mEcDsc;
+    private AccessNetworksManager mAnm;
+    private TransportSelectorCallback mTransportCallback;
+    private EmergencyStateTracker mEmergencyStateTracker;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(this.getClass().getSimpleName());
+
+        mDomainSelectionController = Mockito.mock(DomainSelectionController.class);
+        mConnectionCallback =
+                Mockito.mock(DomainSelectionConnection.DomainSelectionConnectionCallback.class);
+        mEmergencyStateTracker = Mockito.mock(EmergencyStateTracker.class);
+        mAnm = Mockito.mock(AccessNetworksManager.class);
+        doReturn(mAnm).when(mPhone).getAccessNetworksManager();
+        mEcDsc = new EmergencyCallDomainSelectionConnection(mPhone,
+                mDomainSelectionController, mEmergencyStateTracker);
+        mTransportCallback = mEcDsc.getTransportSelectorCallback();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mEcDsc = null;
+        super.tearDown();
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectDomainWifi() throws Exception {
+        doReturn(TRANSPORT_TYPE_WLAN).when(mAnm).getPreferredTransport(anyInt());
+        replaceInstance(EmergencyCallDomainSelectionConnection.class,
+                "mEmergencyStateTracker", mEcDsc, mEmergencyStateTracker);
+
+        EmergencyRegResult regResult = new EmergencyRegResult(
+                EUTRAN, REGISTRATION_STATE_UNKNOWN,
+                NetworkRegistrationInfo.DOMAIN_PS,
+                true, false, 0, 0, "", "", "");
+
+        DomainSelectionService.SelectionAttributes attr =
+                EmergencyCallDomainSelectionConnection.getSelectionAttributes(
+                        mPhone.getPhoneId(), mPhone.getSubId(), false,
+                        TELECOM_CALL_ID1, "911", 0, null, regResult);
+
+        CompletableFuture<Integer> future =
+                mEcDsc.createEmergencyConnection(attr, mConnectionCallback);
+
+        assertNotNull(future);
+        assertFalse(future.isDone());
+
+        verify(mDomainSelectionController).selectDomain(any(), any());
+
+        mTransportCallback.onWlanSelected(true);
+
+        assertTrue(future.isDone());
+        assertEquals((long) DOMAIN_NON_3GPP_PS, (long) future.get());
+        verify(mEmergencyStateTracker).onEmergencyTransportChanged(
+                eq(EmergencyStateTracker.EMERGENCY_TYPE_CALL), eq(MODE_EMERGENCY_WLAN));
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectDomainCs() throws Exception {
+        doReturn(TRANSPORT_TYPE_WWAN).when(mAnm).getPreferredTransport(anyInt());
+        replaceInstance(EmergencyCallDomainSelectionConnection.class,
+                "mEmergencyStateTracker", mEcDsc, mEmergencyStateTracker);
+
+        EmergencyRegResult regResult = new EmergencyRegResult(
+                UTRAN, REGISTRATION_STATE_UNKNOWN,
+                NetworkRegistrationInfo.DOMAIN_CS,
+                true, false, 0, 0, "", "", "");
+
+        DomainSelectionService.SelectionAttributes attr =
+                EmergencyCallDomainSelectionConnection.getSelectionAttributes(
+                        mPhone.getPhoneId(), mPhone.getSubId(), false,
+                        TELECOM_CALL_ID1, "911", 0, null, regResult);
+
+        CompletableFuture<Integer> future =
+                mEcDsc.createEmergencyConnection(attr, mConnectionCallback);
+
+        assertNotNull(future);
+        assertFalse(future.isDone());
+
+        verify(mDomainSelectionController).selectDomain(any(), any());
+
+        WwanSelectorCallback wwanCallback = null;
+        wwanCallback = mTransportCallback.onWwanSelected();
+
+        assertFalse(future.isDone());
+        verify(mEmergencyStateTracker).onEmergencyTransportChanged(
+                eq(EmergencyStateTracker.EMERGENCY_TYPE_CALL), eq(MODE_EMERGENCY_WWAN));
+
+        wwanCallback.onDomainSelected(DOMAIN_CS, false);
+
+        assertTrue(future.isDone());
+        assertEquals((long) DOMAIN_CS, (long) future.get());
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectDomainPs() throws Exception {
+        doReturn(TRANSPORT_TYPE_WWAN).when(mAnm).getPreferredTransport(anyInt());
+        replaceInstance(EmergencyCallDomainSelectionConnection.class,
+                "mEmergencyStateTracker", mEcDsc, mEmergencyStateTracker);
+
+        EmergencyRegResult regResult = new EmergencyRegResult(
+                EUTRAN, REGISTRATION_STATE_UNKNOWN,
+                NetworkRegistrationInfo.DOMAIN_PS,
+                true, true, 0, 0, "", "", "");
+
+        DomainSelectionService.SelectionAttributes attr =
+                EmergencyCallDomainSelectionConnection.getSelectionAttributes(
+                        mPhone.getPhoneId(), mPhone.getSubId(), false,
+                        TELECOM_CALL_ID1, "911", 0, null, regResult);
+
+        CompletableFuture<Integer> future =
+                mEcDsc.createEmergencyConnection(attr, mConnectionCallback);
+
+        assertNotNull(future);
+        assertFalse(future.isDone());
+
+        verify(mDomainSelectionController).selectDomain(any(), any());
+
+        WwanSelectorCallback wwanCallback = null;
+        wwanCallback = mTransportCallback.onWwanSelected();
+
+        assertFalse(future.isDone());
+        verify(mEmergencyStateTracker).onEmergencyTransportChanged(
+                eq(EmergencyStateTracker.EMERGENCY_TYPE_CALL), eq(MODE_EMERGENCY_WWAN));
+
+        wwanCallback.onDomainSelected(DOMAIN_PS, true);
+
+        assertTrue(future.isDone());
+        assertEquals((long) DOMAIN_PS, (long) future.get());
+    }
+
+    @Test
+    @SmallTest
+    public void testOnSelectionTerminated() throws Exception {
+        EmergencyRegResult regResult = new EmergencyRegResult(
+                EUTRAN, REGISTRATION_STATE_UNKNOWN,
+                NetworkRegistrationInfo.DOMAIN_PS,
+                true, true, 0, 0, "", "", "");
+
+        DomainSelectionService.SelectionAttributes attr =
+                EmergencyCallDomainSelectionConnection.getSelectionAttributes(
+                        mPhone.getPhoneId(), mPhone.getSubId(), false,
+                        TELECOM_CALL_ID1, "911", 0, null, regResult);
+
+        mEcDsc.createEmergencyConnection(attr, mConnectionCallback);
+        mTransportCallback.onSelectionTerminated(ERROR_UNSPECIFIED);
+
+        verify(mConnectionCallback).onSelectionTerminated(eq(ERROR_UNSPECIFIED));
+    }
+
+    @Test
+    @SmallTest
+    public void testCancelSelection() throws Exception {
+        mEcDsc.cancelSelection();
+        verify(mAnm).unregisterForQualifiedNetworksChanged(any());
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencySmsDomainSelectionConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencySmsDomainSelectionConnectionTest.java
new file mode 100644
index 0000000..25ccecb
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/EmergencySmsDomainSelectionConnectionTest.java
@@ -0,0 +1,459 @@
+/*
+ * Copyright (C) 2022 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 com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WLAN;
+import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WWAN;
+
+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.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.DomainSelectionService;
+import android.telephony.DomainSelector;
+import android.telephony.NetworkRegistrationInfo;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.data.AccessNetworksManager;
+import com.android.internal.telephony.emergency.EmergencyStateTracker;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Unit tests for EmergencySmsDomainSelectionConnection.
+ */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class EmergencySmsDomainSelectionConnectionTest extends TelephonyTest {
+    private DomainSelectionController mDsController;
+    private DomainSelectionConnection.DomainSelectionConnectionCallback mDscCallback;
+    private DomainSelector mDomainSelector;
+    private EmergencyStateTracker mEmergencyStateTracker;
+
+    private Handler mHandler;
+    private AccessNetworksManager mAnm;
+    private DomainSelectionService.SelectionAttributes mDsAttr;
+    private EmergencySmsDomainSelectionConnection mDsConnection;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(this.getClass().getSimpleName());
+
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+
+        mHandler = new Handler(Looper.myLooper());
+        mDsController = Mockito.mock(DomainSelectionController.class);
+        mDscCallback = Mockito.mock(
+                DomainSelectionConnection.DomainSelectionConnectionCallback.class);
+        mDomainSelector = Mockito.mock(DomainSelector.class);
+        mEmergencyStateTracker = Mockito.mock(EmergencyStateTracker.class);
+        mAnm = Mockito.mock(AccessNetworksManager.class);
+        when(mPhone.getAccessNetworksManager()).thenReturn(mAnm);
+
+        mDsConnection = new EmergencySmsDomainSelectionConnection(
+                mPhone, mDsController, mEmergencyStateTracker);
+        mDsConnection.getTransportSelectorCallback().onCreated(mDomainSelector);
+        mDsAttr = new DomainSelectionService.SelectionAttributes.Builder(
+                mPhone.getPhoneId(), mPhone.getSubId(), DomainSelectionService.SELECTOR_TYPE_SMS)
+                .setEmergency(true)
+                .build();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mDomainSelector = null;
+        mDsAttr = null;
+        mDsConnection = null;
+        mDscCallback = null;
+        mDsController = null;
+        mEmergencyStateTracker = null;
+        super.tearDown();
+    }
+
+    @Test
+    @SmallTest
+    @SuppressWarnings("FutureReturnValueIgnored")
+    public void testOnWlanSelected() throws Exception {
+        when(mAnm.getPreferredTransport(anyInt()))
+                .thenReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+        CompletableFuture<Integer> future =
+                mDsConnection.requestDomainSelection(mDsAttr, mDscCallback);
+        future.thenAcceptAsync((domain) -> {
+            assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain);
+        }, mHandler::post);
+
+        mDsConnection.onWlanSelected(true);
+        processAllMessages();
+
+        assertTrue(future.isDone());
+        verify(mEmergencyStateTracker).onEmergencyTransportChanged(
+                eq(EmergencyStateTracker.EMERGENCY_TYPE_SMS), eq(MODE_EMERGENCY_WLAN));
+    }
+
+    @Test
+    @SmallTest
+    @SuppressWarnings("FutureReturnValueIgnored")
+    public void testOnWlanSelectedWithDifferentTransportType() throws Exception {
+        when(mAnm.getPreferredTransport(anyInt())).thenReturn(
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+        CompletableFuture<Integer> future =
+                mDsConnection.requestDomainSelection(mDsAttr, mDscCallback);
+        future.thenAcceptAsync((domain) -> {
+            assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain);
+        }, mHandler::post);
+
+        mDsConnection.onWlanSelected(true);
+        processAllMessages();
+
+        ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        ArgumentCaptor<Integer> msgCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mEmergencyStateTracker).onEmergencyTransportChanged(
+                eq(EmergencyStateTracker.EMERGENCY_TYPE_SMS), eq(MODE_EMERGENCY_WLAN));
+        verify(mAnm).registerForQualifiedNetworksChanged(
+                handlerCaptor.capture(), msgCaptor.capture());
+        verify(mPhone).notifyEmergencyDomainSelected(
+                eq(AccessNetworkConstants.TRANSPORT_TYPE_WLAN));
+
+        Handler handler = handlerCaptor.getValue();
+        Integer msg = msgCaptor.getValue();
+        handler.handleMessage(Message.obtain(handler, msg.intValue()));
+        processAllMessages();
+
+        assertTrue(future.isDone());
+        verify(mAnm).unregisterForQualifiedNetworksChanged(any(Handler.class));
+    }
+
+    @Test
+    @SmallTest
+    @SuppressWarnings("FutureReturnValueIgnored")
+    public void testOnWlanSelectedWithDifferentTransportTypeAndImsPdn() throws Exception {
+        when(mAnm.getPreferredTransport(anyInt())).thenReturn(
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+        CompletableFuture<Integer> future =
+                mDsConnection.requestDomainSelection(mDsAttr, mDscCallback);
+        future.thenAcceptAsync((domain) -> {
+            assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain);
+        }, mHandler::post);
+
+        mDsConnection.onWlanSelected(false);
+        processAllMessages();
+
+        verify(mEmergencyStateTracker).onEmergencyTransportChanged(
+                eq(EmergencyStateTracker.EMERGENCY_TYPE_SMS), eq(MODE_EMERGENCY_WLAN));
+        verify(mAnm, never()).registerForQualifiedNetworksChanged(any(Handler.class), anyInt());
+        verify(mPhone, never()).notifyEmergencyDomainSelected(anyInt());
+
+        assertTrue(future.isDone());
+    }
+
+    @Test
+    @SmallTest
+    @SuppressWarnings("FutureReturnValueIgnored")
+    public void testOnWlanSelectedWithDifferentTransportTypeWhilePreferredTransportChanged()
+            throws Exception {
+        when(mAnm.getPreferredTransport(anyInt())).thenReturn(
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+        CompletableFuture<Integer> future =
+                mDsConnection.requestDomainSelection(mDsAttr, mDscCallback);
+        future.thenAcceptAsync((domain) -> {
+            assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain);
+        }, mHandler::post);
+
+        mDsConnection.onWlanSelected(true);
+        // When onWlanSelected() is called again,
+        // it will be ignored because the change of preferred transport is in progress.
+        // => onEmergencyTransportChanged() is called only once.
+        mDsConnection.onWlanSelected(true);
+        processAllMessages();
+
+        ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        ArgumentCaptor<Integer> msgCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mEmergencyStateTracker).onEmergencyTransportChanged(
+                eq(EmergencyStateTracker.EMERGENCY_TYPE_SMS), eq(MODE_EMERGENCY_WLAN));
+        verify(mAnm).registerForQualifiedNetworksChanged(
+                handlerCaptor.capture(), msgCaptor.capture());
+        verify(mPhone).notifyEmergencyDomainSelected(
+                eq(AccessNetworkConstants.TRANSPORT_TYPE_WLAN));
+
+        Handler handler = handlerCaptor.getValue();
+        Integer msg = msgCaptor.getValue();
+        handler.handleMessage(Message.obtain(handler, msg.intValue()));
+        processAllMessages();
+
+        assertTrue(future.isDone());
+        verify(mAnm).unregisterForQualifiedNetworksChanged(any(Handler.class));
+    }
+
+    @Test
+    @SmallTest
+    public void testOnWwanSelected() throws Exception {
+        mDsConnection.onWwanSelected();
+
+        verify(mEmergencyStateTracker).onEmergencyTransportChanged(
+                eq(EmergencyStateTracker.EMERGENCY_TYPE_SMS), eq(MODE_EMERGENCY_WWAN));
+    }
+
+    @Test
+    @SmallTest
+    @SuppressWarnings("FutureReturnValueIgnored")
+    public void testOnDomainSelectedPs() throws Exception {
+        when(mAnm.getPreferredTransport(anyInt()))
+                .thenReturn(AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        CompletableFuture<Integer> future =
+                mDsConnection.requestDomainSelection(mDsAttr, mDscCallback);
+        future.thenAcceptAsync((domain) -> {
+            assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain);
+        }, mHandler::post);
+
+        mDsConnection.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS, true);
+        processAllMessages();
+
+        assertTrue(future.isDone());
+    }
+
+    @Test
+    @SmallTest
+    @SuppressWarnings("FutureReturnValueIgnored")
+    public void testOnDomainSelectedPsWithDifferentTransportType() throws Exception {
+        when(mAnm.getPreferredTransport(anyInt())).thenReturn(
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        CompletableFuture<Integer> future =
+                mDsConnection.requestDomainSelection(mDsAttr, mDscCallback);
+        future.thenAcceptAsync((domain) -> {
+            assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain);
+        }, mHandler::post);
+
+        mDsConnection.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS, true);
+        processAllMessages();
+
+        ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        ArgumentCaptor<Integer> msgCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mAnm).registerForQualifiedNetworksChanged(
+                handlerCaptor.capture(), msgCaptor.capture());
+        verify(mPhone).notifyEmergencyDomainSelected(
+                eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN));
+
+        Handler handler = handlerCaptor.getValue();
+        Integer msg = msgCaptor.getValue();
+        handler.handleMessage(Message.obtain(handler, msg.intValue()));
+        processAllMessages();
+
+        assertTrue(future.isDone());
+        verify(mAnm).unregisterForQualifiedNetworksChanged(any(Handler.class));
+    }
+
+    @Test
+    @SmallTest
+    @SuppressWarnings("FutureReturnValueIgnored")
+    public void testOnDomainSelectedPsWithDifferentTransportTypeAndImsPdn() throws Exception {
+        when(mAnm.getPreferredTransport(anyInt())).thenReturn(
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        CompletableFuture<Integer> future =
+                mDsConnection.requestDomainSelection(mDsAttr, mDscCallback);
+        future.thenAcceptAsync((domain) -> {
+            assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain);
+        }, mHandler::post);
+
+        mDsConnection.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS, false);
+        processAllMessages();
+
+        verify(mAnm, never()).registerForQualifiedNetworksChanged(any(Handler.class), anyInt());
+        verify(mPhone, never()).notifyEmergencyDomainSelected(anyInt());
+
+        assertTrue(future.isDone());
+    }
+
+    @Test
+    @SmallTest
+    @SuppressWarnings("FutureReturnValueIgnored")
+    public void testOnDomainSelectedPsWithDifferentTransportTypeAndNotChanged() throws Exception {
+        when(mAnm.getPreferredTransport(anyInt())).thenReturn(
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+        CompletableFuture<Integer> future =
+                mDsConnection.requestDomainSelection(mDsAttr, mDscCallback);
+        future.thenAcceptAsync((domain) -> {
+            assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain);
+        }, mHandler::post);
+
+        mDsConnection.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS, true);
+        processAllMessages();
+
+        ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        ArgumentCaptor<Integer> msgCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mAnm).registerForQualifiedNetworksChanged(
+                handlerCaptor.capture(), msgCaptor.capture());
+        verify(mPhone).notifyEmergencyDomainSelected(
+                eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN));
+
+        Handler handler = handlerCaptor.getValue();
+        Integer msg = msgCaptor.getValue();
+        handler.handleMessage(Message.obtain(handler, msg.intValue()));
+        processAllMessages();
+
+        assertFalse(future.isDone());
+        verify(mAnm, never()).unregisterForQualifiedNetworksChanged(any(Handler.class));
+    }
+
+    @Test
+    @SmallTest
+    @SuppressWarnings("FutureReturnValueIgnored")
+    public void testOnDomainSelectedPsWithDifferentTransportTypeWhilePreferredTransportChanged()
+            throws Exception {
+        when(mAnm.getPreferredTransport(anyInt())).thenReturn(
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        CompletableFuture<Integer> future =
+                mDsConnection.requestDomainSelection(mDsAttr, mDscCallback);
+        future.thenAcceptAsync((domain) -> {
+            assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain);
+        }, mHandler::post);
+
+        mDsConnection.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS, true);
+        // When onDomainSelected() is called again with the different domain,
+        // it will be ignored because the change of preferred transport is in progress.
+        // => The domain selection result is DOMAIN_PS.
+        mDsConnection.onDomainSelected(NetworkRegistrationInfo.DOMAIN_CS, false);
+        processAllMessages();
+
+        ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        ArgumentCaptor<Integer> msgCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mAnm).registerForQualifiedNetworksChanged(
+                handlerCaptor.capture(), msgCaptor.capture());
+        verify(mPhone).notifyEmergencyDomainSelected(
+                eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN));
+
+        Handler handler = handlerCaptor.getValue();
+        Integer msg = msgCaptor.getValue();
+        handler.handleMessage(Message.obtain(handler, msg.intValue()));
+        processAllMessages();
+
+        assertTrue(future.isDone());
+        verify(mAnm).unregisterForQualifiedNetworksChanged(any(Handler.class));
+    }
+
+    @Test
+    @SmallTest
+    @SuppressWarnings("FutureReturnValueIgnored")
+    public void testOnDomainSelectedCs() throws Exception {
+        when(mAnm.getPreferredTransport(anyInt()))
+                .thenReturn(AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        CompletableFuture<Integer> future =
+                mDsConnection.requestDomainSelection(mDsAttr, mDscCallback);
+        future.thenAcceptAsync((domain) -> {
+            assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_CS), domain);
+        }, mHandler::post);
+
+        mDsConnection.onDomainSelected(NetworkRegistrationInfo.DOMAIN_CS, false);
+        processAllMessages();
+
+        assertTrue(future.isDone());
+    }
+
+    @Test
+    @SmallTest
+    @SuppressWarnings("FutureReturnValueIgnored")
+    public void testFinishSelection() throws Exception {
+        when(mAnm.getPreferredTransport(anyInt())).thenReturn(
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        CompletableFuture<Integer> future =
+                mDsConnection.requestDomainSelection(mDsAttr, mDscCallback);
+        future.thenAcceptAsync((domain) -> {
+            assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain);
+        }, mHandler::post);
+
+        mDsConnection.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS, true);
+        processAllMessages();
+
+        ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        ArgumentCaptor<Integer> msgCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mAnm).registerForQualifiedNetworksChanged(
+                handlerCaptor.capture(), msgCaptor.capture());
+        verify(mPhone).notifyEmergencyDomainSelected(
+                eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN));
+
+        mDsConnection.finishSelection();
+        processAllMessages();
+
+        assertFalse(future.isDone());
+        verify(mAnm).unregisterForQualifiedNetworksChanged(any(Handler.class));
+        verify(mDomainSelector).cancelSelection();
+    }
+
+    @Test
+    @SmallTest
+    @SuppressWarnings("FutureReturnValueIgnored")
+    public void testFinishSelectionAfterDomainSelectionCompleted() throws Exception {
+        when(mAnm.getPreferredTransport(anyInt())).thenReturn(
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        CompletableFuture<Integer> future =
+                mDsConnection.requestDomainSelection(mDsAttr, mDscCallback);
+        future.thenAcceptAsync((domain) -> {
+            assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain);
+        }, mHandler::post);
+
+        mDsConnection.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS, true);
+        processAllMessages();
+
+        ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        ArgumentCaptor<Integer> msgCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mAnm).registerForQualifiedNetworksChanged(
+                handlerCaptor.capture(), msgCaptor.capture());
+        verify(mPhone).notifyEmergencyDomainSelected(
+                eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN));
+
+        Handler handler = handlerCaptor.getValue();
+        Integer msg = msgCaptor.getValue();
+        handler.handleMessage(Message.obtain(handler, msg.intValue()));
+        processAllMessages();
+        mDsConnection.finishSelection();
+
+        assertTrue(future.isDone());
+        // This method should be invoked one time.
+        verify(mAnm).unregisterForQualifiedNetworksChanged(any(Handler.class));
+        verify(mDomainSelector).finishSelection();
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnectionTest.java
new file mode 100644
index 0000000..0403232
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnectionTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2022 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 android.telephony.DisconnectCause.ERROR_UNSPECIFIED;
+import static android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_CS;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+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 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.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.CompletableFuture;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NormalCallDomainSelectionConnectionTest extends TelephonyTest {
+
+    private static final String TELECOM_CALL_ID1 = "TC1";
+
+    @Mock
+    private DomainSelectionController mMockDomainSelectionController;
+    @Mock
+    private DomainSelectionConnection.DomainSelectionConnectionCallback mMockConnectionCallback;
+    @Mock
+    private AccessNetworksManager mMockAccessNetworksManager;
+
+    private TransportSelectorCallback mTransportCallback;
+    private NormalCallDomainSelectionConnection mNormalCallDomainSelectionConnection;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(this.getClass().getSimpleName());
+        MockitoAnnotations.initMocks(this);
+        doReturn(mMockAccessNetworksManager).when(mPhone).getAccessNetworksManager();
+        mNormalCallDomainSelectionConnection =
+                new NormalCallDomainSelectionConnection(mPhone, mMockDomainSelectionController);
+        mTransportCallback = mNormalCallDomainSelectionConnection.getTransportSelectorCallback();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mNormalCallDomainSelectionConnection = null;
+        super.tearDown();
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectDomainWifi() throws Exception {
+        DomainSelectionService.SelectionAttributes attributes =
+                NormalCallDomainSelectionConnection.getSelectionAttributes(mPhone.getPhoneId(),
+                        mPhone.getSubId(), TELECOM_CALL_ID1, "123", false, 0, null);
+
+        CompletableFuture<Integer> future =
+                mNormalCallDomainSelectionConnection
+                        .createNormalConnection(attributes, mMockConnectionCallback);
+
+        assertNotNull(future);
+        assertFalse(future.isDone());
+
+        verify(mMockDomainSelectionController).selectDomain(any(), any());
+
+        mTransportCallback.onWlanSelected(false);
+
+        assertTrue(future.isDone());
+        assertEquals((long) DOMAIN_PS, (long) future.get());
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectDomainCs() throws Exception {
+        DomainSelectionService.SelectionAttributes attributes =
+                NormalCallDomainSelectionConnection.getSelectionAttributes(mPhone.getPhoneId(),
+                        mPhone.getSubId(), TELECOM_CALL_ID1, "123", false, 0, null);
+
+        CompletableFuture<Integer> future =
+                mNormalCallDomainSelectionConnection
+                        .createNormalConnection(attributes, mMockConnectionCallback);
+
+        assertNotNull(future);
+        assertFalse(future.isDone());
+
+        verify(mMockDomainSelectionController).selectDomain(any(), any());
+
+        WwanSelectorCallback wwanCallback = mTransportCallback.onWwanSelected();
+
+        assertFalse(future.isDone());
+        wwanCallback.onDomainSelected(DOMAIN_CS, false);
+
+        assertTrue(future.isDone());
+        assertEquals((long) DOMAIN_CS, (long) future.get());
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectDomainPs() throws Exception {
+        DomainSelectionService.SelectionAttributes attributes =
+                NormalCallDomainSelectionConnection.getSelectionAttributes(mPhone.getPhoneId(),
+                        mPhone.getSubId(), TELECOM_CALL_ID1, "123", false, 0, null);
+
+        CompletableFuture<Integer> future =
+                mNormalCallDomainSelectionConnection
+                        .createNormalConnection(attributes, mMockConnectionCallback);
+
+        assertNotNull(future);
+        assertFalse(future.isDone());
+
+        verify(mMockDomainSelectionController).selectDomain(any(), any());
+
+        WwanSelectorCallback wwanCallback = mTransportCallback.onWwanSelected();
+
+        assertFalse(future.isDone());
+        wwanCallback.onDomainSelected(DOMAIN_PS, false);
+
+        assertTrue(future.isDone());
+        assertEquals((long) DOMAIN_PS, (long) future.get());
+    }
+
+    @Test
+    @SmallTest
+    public void testOnSelectionTerminated() throws Exception {
+        DomainSelectionService.SelectionAttributes attributes =
+                NormalCallDomainSelectionConnection.getSelectionAttributes(mPhone.getPhoneId(),
+                        mPhone.getSubId(), TELECOM_CALL_ID1, "123", false, 0, null);
+
+        CompletableFuture<Integer> future = mNormalCallDomainSelectionConnection
+                .createNormalConnection(attributes, mMockConnectionCallback);
+        mTransportCallback.onSelectionTerminated(ERROR_UNSPECIFIED);
+
+        verify(mMockConnectionCallback).onSelectionTerminated(eq(ERROR_UNSPECIFIED));
+        assertNotNull(future);
+    }
+
+    @Test
+    public void testGetSelectionAttributes() throws Exception {
+        ImsReasonInfo imsReasonInfo = new ImsReasonInfo();
+        DomainSelectionService.SelectionAttributes attributes =
+                NormalCallDomainSelectionConnection.getSelectionAttributes(1, 2,
+                        TELECOM_CALL_ID1, "123", false, 10, imsReasonInfo);
+
+        assertEquals(1, attributes.getSlotId());
+        assertEquals(2, attributes.getSubId());
+        assertEquals(TELECOM_CALL_ID1, attributes.getCallId());
+        assertEquals("123", attributes.getNumber());
+        assertEquals(false, attributes.isVideoCall());
+        assertEquals(false, attributes.isEmergency());
+        assertEquals(SELECTOR_TYPE_CALLING, attributes.getSelectorType());
+        assertEquals(10, attributes.getCsDisconnectCause());
+        assertEquals(imsReasonInfo, attributes.getPsDisconnectCause());
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/domainselection/SmsDomainSelectionConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/domainselection/SmsDomainSelectionConnectionTest.java
new file mode 100644
index 0000000..e4afa79
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/SmsDomainSelectionConnectionTest.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2022 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+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.runner.AndroidJUnit4;
+
+import com.android.internal.telephony.Phone;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Unit tests for SmsDomainSelectionConnection.
+ */
+@RunWith(AndroidJUnit4.class)
+public class SmsDomainSelectionConnectionTest {
+    private static final int SLOT_ID = 0;
+    private static final int SUB_ID = 1;
+
+    @Mock private Phone mPhone;
+    @Mock private DomainSelectionController mDsController;
+    @Mock private DomainSelectionConnection.DomainSelectionConnectionCallback mDscCallback;
+    @Mock private DomainSelector mDomainSelector;
+
+    private Handler mHandler;
+    private TestableLooper mTestableLooper;
+    private DomainSelectionService.SelectionAttributes mDsAttr;
+    private SmsDomainSelectionConnection mDsConnection;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        HandlerThread handlerThread = new HandlerThread(
+                SmsDomainSelectionConnectionTest.class.getSimpleName());
+        handlerThread.start();
+
+        mHandler = new Handler(handlerThread.getLooper());
+        mDsConnection = new SmsDomainSelectionConnection(mPhone, mDsController);
+        mDsConnection.getTransportSelectorCallback().onCreated(mDomainSelector);
+        mDsAttr = new DomainSelectionService.SelectionAttributes.Builder(
+                SLOT_ID, SUB_ID, DomainSelectionService.SELECTOR_TYPE_SMS).build();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mTestableLooper != null) {
+            mTestableLooper.destroy();
+            mTestableLooper = null;
+        }
+
+        if (mHandler != null) {
+            mHandler.getLooper().quit();
+            mHandler = null;
+        }
+
+        mDomainSelector = null;
+        mDsAttr = null;
+        mDsConnection = null;
+        mDscCallback = null;
+        mDsController = null;
+        mPhone = null;
+    }
+
+    @Test
+    @SmallTest
+    public void testRequestDomainSelection() {
+        CompletableFuture<Integer> future =
+                mDsConnection.requestDomainSelection(mDsAttr, mDscCallback);
+
+        assertNotNull(future);
+        verify(mDsController).selectDomain(eq(mDsAttr), any(TransportSelectorCallback.class));
+    }
+
+    @Test
+    @SmallTest
+    @SuppressWarnings("FutureReturnValueIgnored")
+    public void testOnWlanSelected() throws Exception {
+        setUpTestableLooper();
+        CompletableFuture<Integer> future =
+                mDsConnection.requestDomainSelection(mDsAttr, mDscCallback);
+        future.thenAcceptAsync((domain) -> {
+            assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain);
+        }, mHandler::post);
+
+        mDsConnection.onWlanSelected();
+        processAllMessages();
+
+        assertTrue(future.isDone());
+    }
+
+    @Test
+    @SmallTest
+    public void testOnSelectionTerminated() {
+        CompletableFuture<Integer> future =
+                mDsConnection.requestDomainSelection(mDsAttr, mDscCallback);
+        mDsConnection.onSelectionTerminated(DisconnectCause.LOCAL);
+
+        assertFalse(future.isDone());
+        verify(mDscCallback).onSelectionTerminated(eq(DisconnectCause.LOCAL));
+    }
+
+    @Test
+    @SmallTest
+    @SuppressWarnings("FutureReturnValueIgnored")
+    public void testOnDomainSelectedPs() throws Exception {
+        setUpTestableLooper();
+        CompletableFuture<Integer> future =
+                mDsConnection.requestDomainSelection(mDsAttr, mDscCallback);
+        future.thenAcceptAsync((domain) -> {
+            assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain);
+        }, mHandler::post);
+
+        mDsConnection.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS);
+        processAllMessages();
+
+        assertTrue(future.isDone());
+    }
+
+    @Test
+    @SmallTest
+    @SuppressWarnings("FutureReturnValueIgnored")
+    public void testOnDomainSelectedCs() throws Exception {
+        setUpTestableLooper();
+        CompletableFuture<Integer> future =
+                mDsConnection.requestDomainSelection(mDsAttr, mDscCallback);
+        future.thenAcceptAsync((domain) -> {
+            assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_CS), domain);
+        }, mHandler::post);
+
+        mDsConnection.onDomainSelected(NetworkRegistrationInfo.DOMAIN_CS);
+        processAllMessages();
+
+        assertTrue(future.isDone());
+    }
+
+    @Test
+    @SmallTest
+    @SuppressWarnings("FutureReturnValueIgnored")
+    public void testFinishSelection() throws Exception {
+        setUpTestableLooper();
+        CompletableFuture<Integer> future =
+                mDsConnection.requestDomainSelection(mDsAttr, mDscCallback);
+        future.thenAcceptAsync((domain) -> {
+            assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain);
+        }, mHandler::post);
+
+        mDsConnection.onDomainSelected(NetworkRegistrationInfo.DOMAIN_PS);
+        processAllMessages();
+        mDsConnection.finishSelection();
+
+        verify(mDomainSelector).finishSelection();
+    }
+
+    @Test
+    @SmallTest
+    @SuppressWarnings("FutureReturnValueIgnored")
+    public void testCancelSelection() throws Exception {
+        CompletableFuture<Integer> future =
+                mDsConnection.requestDomainSelection(mDsAttr, mDscCallback);
+        future.thenAcceptAsync((domain) -> {
+            assertEquals(Integer.valueOf(NetworkRegistrationInfo.DOMAIN_PS), domain);
+        }, mHandler::post);
+
+        mDsConnection.finishSelection();
+
+        verify(mDomainSelector).cancelSelection();
+    }
+
+    private void setUpTestableLooper() throws Exception {
+        mTestableLooper = new TestableLooper(mHandler.getLooper());
+    }
+
+    private void processAllMessages() {
+        while (!mTestableLooper.getLooper().getQueue().isIdle()) {
+            mTestableLooper.processAllMessages();
+        }
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTest.java b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTest.java
index 1273148..5653209 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTest.java
@@ -261,7 +261,7 @@
                 EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
                 EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
 
-        assertFalse(EmergencyNumber.areSameEmergencyNumbers(num1, num2));
+        assertFalse(EmergencyNumber.areSameEmergencyNumbers(num1, num2, false));
     }
 
 
@@ -284,7 +284,7 @@
                 EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
                 EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
 
-        assertFalse(EmergencyNumber.areSameEmergencyNumbers(num1, num2));
+        assertFalse(EmergencyNumber.areSameEmergencyNumbers(num1, num2, false));
     }
 
     public void testSameEmergencyNumberDifferentMnc() throws Exception {
@@ -306,7 +306,7 @@
                 EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
                 EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
 
-        assertFalse(EmergencyNumber.areSameEmergencyNumbers(num1, num2));
+        assertFalse(EmergencyNumber.areSameEmergencyNumbers(num1, num2, false));
     }
 
     public void testSameEmergencyNumberDifferentCategories() throws Exception {
@@ -328,7 +328,7 @@
                 EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
                 EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
 
-        assertFalse(EmergencyNumber.areSameEmergencyNumbers(num1, num2));
+        assertFalse(EmergencyNumber.areSameEmergencyNumbers(num1, num2, false));
     }
 
     public void testSameEmergencyNumberDifferentUrns() throws Exception {
@@ -357,7 +357,7 @@
                 EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
                 EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
 
-        assertFalse(EmergencyNumber.areSameEmergencyNumbers(num1, num2));
+        assertFalse(EmergencyNumber.areSameEmergencyNumbers(num1, num2, false));
     }
 
     public void testSameEmergencyNumberCallRouting() throws Exception {
@@ -379,7 +379,36 @@
                 EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
                 EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
 
-        assertFalse(EmergencyNumber.areSameEmergencyNumbers(num1, num2));
+        /* case 1: Check routing is not checked when comparing the same numbers. As routing will
+        be unknown for all numbers apart from DB. Check merge when both are not from DB then
+        routing value is merged from first number. */
+        assertTrue(EmergencyNumber.areSameEmergencyNumbers(num1, num2, false));
+        assertEquals(num1, EmergencyNumber.mergeSameEmergencyNumbers(num1, num2));
+
+        num2 = new EmergencyNumber(
+                "911",
+                "jp",
+                "30",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+                new ArrayList<String>(),
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
+
+        /* case 1: Check routing is not checked when comparing the same numbers. Check merge when
+        one of the number is from DB then routing value is merged from DB number. Along with
+        source value is masked with both*/
+        assertTrue(EmergencyNumber.areSameEmergencyNumbers(num1, num2, false));
+
+        num2 = new EmergencyNumber(
+                "911",
+                "jp",
+                "30",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+                new ArrayList<String>(),
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING
+                | EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
+        assertEquals(num2, EmergencyNumber.mergeSameEmergencyNumbers(num1, num2));
     }
 
     public void testSameEmergencyNumberDifferentSource() throws Exception {
@@ -401,7 +430,7 @@
                 EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
                 EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
 
-        assertTrue(EmergencyNumber.areSameEmergencyNumbers(num1, num2));
+        assertTrue(EmergencyNumber.areSameEmergencyNumbers(num1, num2, false));
     }
 
     public void testSameEmergencyNumberDifferentSourceTestOrNot() throws Exception {
@@ -423,7 +452,7 @@
                 EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST,
                 EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
 
-        assertFalse(EmergencyNumber.areSameEmergencyNumbers(num1, num2));
+        assertFalse(EmergencyNumber.areSameEmergencyNumbers(num1, num2, false));
     }
 
     public void testMergeSameNumbersInEmergencyNumberListWithDifferentSources() throws Exception {
@@ -595,4 +624,246 @@
 
         assertEquals(outputNumberList, inputNumberList);
     }
+
+    public void testMergeSameNumbersEmergencyNumberListByDeterminingFields() throws Exception {
+        List<String> urn1 = new ArrayList<>();
+        urn1.add("sos");
+
+        List<String> urn2 = new ArrayList<>();
+        urn2.add("sos:ambulance");
+
+        List<EmergencyNumber> inputNumberList = new ArrayList<>();
+        EmergencyNumber num1 = new EmergencyNumber(
+                "110",
+                "jp",
+                "30",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+                new ArrayList<String>(),
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
+
+        EmergencyNumber num2 = new EmergencyNumber(
+                "110",
+                "jp",
+                "30",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE,
+                urn1,
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+
+        EmergencyNumber num3 = new EmergencyNumber(
+                "911",
+                "us",
+                "30",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+                new ArrayList<String>(),
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+
+        EmergencyNumber num4 = new EmergencyNumber(
+                "911",
+                "us",
+                "30",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AMBULANCE,
+                urn2,
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY);
+
+        EmergencyNumber num5 = new EmergencyNumber(
+                "112",
+                "in",
+                "",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE,
+                urn2,
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
+
+        EmergencyNumber num6 = new EmergencyNumber(
+                "112",
+                "in",
+                "",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+                urn1,
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_SIM,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+
+        EmergencyNumber num7 = new EmergencyNumber(
+                "108",
+                "in",
+                "",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE,
+                urn2,
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+
+        EmergencyNumber num8 = new EmergencyNumber(
+                "108",
+                "in",
+                "",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+                new ArrayList<String>(),
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_SIM,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+
+        EmergencyNumber num9 = new EmergencyNumber(
+                "102",
+                "in",
+                "",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE,
+                urn2,
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+
+        EmergencyNumber num10 = new EmergencyNumber(
+                "102",
+                "in",
+                "",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MOUNTAIN_RESCUE,
+                urn1,
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_SIM,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+
+        EmergencyNumber num11 = new EmergencyNumber(
+                "100",
+                "in",
+                "",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_MIEC,
+                urn2,
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY);
+
+        EmergencyNumber num12 = new EmergencyNumber(
+                "100",
+                "in",
+                "",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE,
+                new ArrayList<String>(),
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_SIM,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+
+
+        EmergencyNumber num13 = new EmergencyNumber(
+                "200",
+                "in",
+                "",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+                new ArrayList<String>(),
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY);
+
+        EmergencyNumber num14 = new EmergencyNumber(
+                "200",
+                "in",
+                "",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE,
+                new ArrayList<String>(),
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+
+        inputNumberList.add(num1);
+        inputNumberList.add(num2);
+        inputNumberList.add(num3);
+        inputNumberList.add(num4);
+        inputNumberList.add(num5);
+        inputNumberList.add(num6);
+        inputNumberList.add(num7);
+        inputNumberList.add(num8);
+        inputNumberList.add(num9);
+        inputNumberList.add(num10);
+        inputNumberList.add(num11);
+        inputNumberList.add(num12);
+        inputNumberList.add(num13);
+        inputNumberList.add(num14);
+
+        EmergencyNumber mergeOfNum1AndNum2 = new EmergencyNumber(
+                "110",
+                "jp",
+                "30",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE,
+                urn1,
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE
+                | EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
+
+        EmergencyNumber mergeOfNum3AndNum4 = new EmergencyNumber(
+                "911",
+                "us",
+                "30",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AMBULANCE,
+                urn2,
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE
+                | EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY);
+
+        List<String> mergedUrns1And2 = new ArrayList<>();
+        mergedUrns1And2.add("sos");
+        mergedUrns1And2.add("sos:ambulance");
+
+        EmergencyNumber mergeOfNum5AndNum6 = new EmergencyNumber(
+                "112",
+                "in",
+                "",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE,
+                mergedUrns1And2,
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE
+                | EmergencyNumber.EMERGENCY_NUMBER_SOURCE_SIM,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
+
+        EmergencyNumber mergeOfNum7AndNum8 = new EmergencyNumber(
+                "108",
+                "in",
+                "",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE,
+                urn2,
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG
+                | EmergencyNumber.EMERGENCY_NUMBER_SOURCE_SIM,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+
+        EmergencyNumber mergeOfNum11AndNum12 = new EmergencyNumber(
+                "100",
+                "in",
+                "",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE,
+                urn2,
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_SIM
+                | EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY);
+
+        List<String> mergedUrns2And1 = new ArrayList<>();
+        mergedUrns2And1.add("sos:ambulance");
+        mergedUrns2And1.add("sos");
+
+        EmergencyNumber mergeOfNum9AndNum10 = new EmergencyNumber(
+                "102",
+                "in",
+                "",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE,
+                mergedUrns2And1,
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING
+                | EmergencyNumber.EMERGENCY_NUMBER_SOURCE_SIM,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+
+        EmergencyNumber mergeOfNum13AndNum14 = new EmergencyNumber(
+                "200",
+                "in",
+                "",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE,
+                new ArrayList<String>(),
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG
+                | EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY);
+
+        List<EmergencyNumber> outputNumberList = new ArrayList<>();
+        outputNumberList.add(mergeOfNum1AndNum2);
+        outputNumberList.add(mergeOfNum3AndNum4);
+        outputNumberList.add(mergeOfNum5AndNum6);
+        outputNumberList.add(mergeOfNum7AndNum8);
+        outputNumberList.add(mergeOfNum9AndNum10);
+        outputNumberList.add(mergeOfNum11AndNum12);
+        outputNumberList.add(mergeOfNum13AndNum14);
+        Collections.sort(outputNumberList);
+
+        EmergencyNumber.mergeSameNumbersInEmergencyNumberList(inputNumberList, true);
+        assertEquals(outputNumberList, inputNumberList);
+    }
 }
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 0e0bda8..c47eb3b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyNumberTrackerTest.java
@@ -20,27 +20,38 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.IntentFilter;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
 import android.os.AsyncResult;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.telephony.emergency.EmergencyNumber;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
 import androidx.test.InstrumentationRegistry;
 
-import com.android.internal.telephony.HalVersion;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
-import com.android.internal.telephony.SubscriptionController;
+import com.android.internal.telephony.ServiceStateTracker;
 import com.android.internal.telephony.TelephonyTest;
 
 import com.google.i18n.phonenumbers.ShortNumberInfo;
@@ -49,6 +60,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
 
 import java.io.BufferedInputStream;
 import java.io.File;
@@ -74,6 +87,7 @@
     private static final String EMERGENCY_NUMBER_DB_OTA_FILE = "eccdata_ota";
     private static final int CONFIG_UNIT_TEST_EMERGENCY_NUMBER_DB_VERSION = 99999;
     private static final String CONFIG_EMERGENCY_NUMBER_ADDRESS = "54321";
+    private static final String CONFIG_EMERGENCY_DUPLICATE_NUMBER = "4321";
     private static final String CONFIG_EMERGENCY_NUMBER_COUNTRY = "us";
     private static final String CONFIG_EMERGENCY_NUMBER_MNC = "";
     private static final String NON_3GPP_EMERGENCY_TEST_NUMBER = "9876543";
@@ -102,7 +116,7 @@
     private static final int INVALID_SLOT_INDEX_VALID = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
     private ParcelFileDescriptor mOtaParcelFileDescriptor = null;
     // Mocked classes
-    private SubscriptionController mSubControllerMock;
+    private CarrierConfigManager mCarrierConfigManagerMock;
 
     // mEmergencyNumberTrackerMock for mPhone
     private EmergencyNumberTracker mEmergencyNumberTrackerMock;
@@ -115,14 +129,19 @@
 
     private File mLocalDownloadDirectory;
     private ShortNumberInfo mShortNumberInfo;
+    private Context mMockContext;
+    private Resources mResources;
 
     @Before
     public void setUp() throws Exception {
         logd("EmergencyNumberTrackerTest +Setup!");
         super.setUp(getClass().getSimpleName());
         mShortNumberInfo = mock(ShortNumberInfo.class);
-        mSubControllerMock = mock(SubscriptionController.class);
-        mContext = InstrumentationRegistry.getTargetContext();
+        mCarrierConfigManagerMock = mock(CarrierConfigManager.class);
+
+        mContext = new ContextWrapper(InstrumentationRegistry.getTargetContext());
+        mMockContext = mock(Context.class);
+        mResources = mock(Resources.class);
 
         doReturn(mContext).when(mPhone).getContext();
         doReturn(0).when(mPhone).getPhoneId();
@@ -141,6 +160,9 @@
         // Copy an OTA file to the test directory to similate the OTA mechanism
         simulateOtaEmergencyNumberDb(mPhone);
 
+        AssetManager am = new AssetManager.Builder().build();
+        doReturn(am).when(mMockContext).getAssets();
+
         processAllMessages();
         logd("EmergencyNumberTrackerTest -Setup!");
     }
@@ -181,6 +203,14 @@
             EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
             EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
         mEmergencyNumberListTestSample.add(emergencyNumberForTest);
+
+        emergencyNumberForTest = new EmergencyNumber(
+                CONFIG_EMERGENCY_DUPLICATE_NUMBER, CONFIG_EMERGENCY_NUMBER_COUNTRY,
+                "", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES,
+                CONFIG_EMERGENCY_NUMBER_SERVICE_URNS,
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+        mEmergencyNumberListTestSample.add(emergencyNumberForTest);
     }
 
     private void sendEmergencyNumberListFromRadio() {
@@ -267,6 +297,11 @@
         }
     }
 
+    private boolean hasDbEmergencyNumbers(List<EmergencyNumber> subList,
+            List<EmergencyNumber> list) {
+        return list.containsAll(subList);
+    }
+
     private boolean hasDbEmergencyNumber(EmergencyNumber number, List<EmergencyNumber> list) {
         return list.contains(number);
     }
@@ -297,13 +332,6 @@
     @Test
     public void testIsSimAbsent() throws Exception {
         setDsdsPhones();
-        replaceInstance(SubscriptionController.class, "sInstance", null, mSubControllerMock);
-
-        // Both sim slots are active
-        doReturn(VALID_SLOT_INDEX_VALID_1).when(mSubControllerMock).getSlotIndex(
-                eq(SUB_ID_PHONE_1));
-        doReturn(VALID_SLOT_INDEX_VALID_2).when(mSubControllerMock).getSlotIndex(
-                eq(SUB_ID_PHONE_2));
         doReturn(VALID_SLOT_INDEX_VALID_1).when(mSubscriptionManagerService).getSlotIndex(
                 eq(SUB_ID_PHONE_1));
         doReturn(VALID_SLOT_INDEX_VALID_2).when(mSubscriptionManagerService).getSlotIndex(
@@ -311,10 +339,6 @@
         assertFalse(mEmergencyNumberTrackerMock.isSimAbsent());
 
         // One sim slot is active; the other one is not active
-        doReturn(VALID_SLOT_INDEX_VALID_1).when(mSubControllerMock).getSlotIndex(
-                eq(SUB_ID_PHONE_1));
-        doReturn(INVALID_SLOT_INDEX_VALID).when(mSubControllerMock).getSlotIndex(
-                eq(SUB_ID_PHONE_2));
         doReturn(VALID_SLOT_INDEX_VALID_1).when(mSubscriptionManagerService).getSlotIndex(
                 eq(SUB_ID_PHONE_1));
         doReturn(INVALID_SLOT_INDEX_VALID).when(mSubscriptionManagerService).getSlotIndex(
@@ -322,10 +346,6 @@
         assertFalse(mEmergencyNumberTrackerMock.isSimAbsent());
 
         // Both sim slots are not active
-        doReturn(INVALID_SLOT_INDEX_VALID).when(mSubControllerMock).getSlotIndex(
-                eq(SUB_ID_PHONE_1));
-        doReturn(INVALID_SLOT_INDEX_VALID).when(mSubControllerMock).getSlotIndex(
-                eq(SUB_ID_PHONE_2));
         doReturn(INVALID_SLOT_INDEX_VALID).when(mSubscriptionManagerService).getSlotIndex(
                 anyInt());
         assertTrue(mEmergencyNumberTrackerMock.isSimAbsent());
@@ -339,7 +359,42 @@
     }
 
     @Test
-    public void testUpdateEmergencyCountryIso() throws Exception {
+    public void testRegistrationForCountryChangeIntent() throws Exception {
+        EmergencyNumberTracker localEmergencyNumberTracker;
+        Context spyContext = spy(mContext);
+        doReturn(spyContext).when(mPhone).getContext();
+        ArgumentCaptor<IntentFilter> intentCaptor = ArgumentCaptor.forClass(IntentFilter.class);
+
+        localEmergencyNumberTracker = new EmergencyNumberTracker(mPhone, mSimulatedCommands);
+        verify(spyContext, times(1)).registerReceiver(any(), intentCaptor.capture());
+        IntentFilter ifilter = intentCaptor.getValue();
+        assertTrue(ifilter.hasAction(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED));
+    }
+
+    @Test
+    public void testUpdateEmergencyCountryIso_whenStatePowerOff() throws Exception {
+        testUpdateEmergencyCountryIso(ServiceState.STATE_POWER_OFF);
+    }
+
+    @Test
+    public void testUpdateEmergencyCountryIso_whenStateInService() throws Exception {
+        testUpdateEmergencyCountryIso(ServiceState.STATE_IN_SERVICE);
+    }
+
+    @Test
+    public void testUpdateEmergencyCountryIso_whenStateOos() throws Exception {
+        testUpdateEmergencyCountryIso(ServiceState.STATE_OUT_OF_SERVICE);
+    }
+
+    @Test
+    public void testUpdateEmergencyCountryIso_whenStateEmergencyOnly() throws Exception {
+        testUpdateEmergencyCountryIso(ServiceState.STATE_EMERGENCY_ONLY);
+    }
+
+    private void testUpdateEmergencyCountryIso(int ss) throws Exception {
+        doReturn(mLocaleTracker).when(mSST).getLocaleTracker();
+        doReturn("us").when(mLocaleTracker).getLastKnownCountryIso();
+
         sendEmergencyNumberPrefix(mEmergencyNumberTrackerMock);
 
         mEmergencyNumberTrackerMock.updateEmergencyNumberDatabaseCountryChange("us");
@@ -347,10 +402,14 @@
         assertTrue(mEmergencyNumberTrackerMock.getEmergencyCountryIso().equals("us"));
         assertTrue(mEmergencyNumberTrackerMock.getLastKnownEmergencyCountryIso().equals("us"));
 
+        doReturn(ss).when(mServiceState).getState();
         mEmergencyNumberTrackerMock.updateEmergencyNumberDatabaseCountryChange("");
         processAllMessages();
         assertTrue(mEmergencyNumberTrackerMock.getEmergencyCountryIso().equals(""));
         assertTrue(mEmergencyNumberTrackerMock.getLastKnownEmergencyCountryIso().equals("us"));
+
+        //make sure we look up cached location whenever current iso is null
+        verify(mLocaleTracker).getLastKnownCountryIso();
     }
 
     @Test
@@ -376,18 +435,9 @@
 
     @Test
     public void testIsEmergencyNumber_FallbackToShortNumberXml_NoSims() throws Exception {
-        // Set up the Hal version as 1.4
-        doReturn(new HalVersion(1, 4)).when(mPhone).getHalVersion();
-        doReturn(new HalVersion(1, 4)).when(mPhone2).getHalVersion();
-
         setDsdsPhones();
-        replaceInstance(SubscriptionController.class, "sInstance", null, mSubControllerMock);
 
         // Both sim slots are not active
-        doReturn(INVALID_SLOT_INDEX_VALID).when(mSubControllerMock).getSlotIndex(
-            eq(SUB_ID_PHONE_1));
-        doReturn(INVALID_SLOT_INDEX_VALID).when(mSubControllerMock).getSlotIndex(
-            eq(SUB_ID_PHONE_2));
         doReturn(INVALID_SLOT_INDEX_VALID).when(mSubscriptionManagerService).getSlotIndex(
                 anyInt());
         assertTrue(mEmergencyNumberTrackerMock.isSimAbsent());
@@ -399,7 +449,7 @@
         processAllMessages();
 
         replaceInstance(ShortNumberInfo.class, "INSTANCE", null, mShortNumberInfo);
-        mEmergencyNumberTrackerMock.isEmergencyNumber(NON_3GPP_EMERGENCY_TEST_NUMBER, true);
+        mEmergencyNumberTrackerMock.isEmergencyNumber(NON_3GPP_EMERGENCY_TEST_NUMBER);
 
         //verify that we fall back to shortnumber xml when there are no SIMs
         verify(mShortNumberInfo).isEmergencyNumber(NON_3GPP_EMERGENCY_TEST_NUMBER, "JP");
@@ -416,30 +466,17 @@
     }
 
     private void testIsEmergencyNumber_NoFallbackToShortNumberXml(int numSims) throws Exception {
-        // Set up the Hal version as 1.4
-        doReturn(new HalVersion(1, 4)).when(mPhone).getHalVersion();
-        doReturn(new HalVersion(1, 4)).when(mPhone2).getHalVersion();
-
         assertTrue((numSims > 0 && numSims < 3));
         setDsdsPhones();
-        replaceInstance(SubscriptionController.class, "sInstance", null, mSubControllerMock);
 
         if (numSims == 1) {
             // One sim slot is active; the other one is not active
-            doReturn(VALID_SLOT_INDEX_VALID_1).when(mSubControllerMock).getSlotIndex(
-                eq(SUB_ID_PHONE_1));
-            doReturn(INVALID_SLOT_INDEX_VALID).when(mSubControllerMock).getSlotIndex(
-                eq(SUB_ID_PHONE_2));
             doReturn(VALID_SLOT_INDEX_VALID_1).when(mSubscriptionManagerService).getSlotIndex(
                     eq(SUB_ID_PHONE_1));
             doReturn(INVALID_SLOT_INDEX_VALID).when(mSubscriptionManagerService).getSlotIndex(
                     eq(SUB_ID_PHONE_2));
         } else {
             //both slots active
-            doReturn(VALID_SLOT_INDEX_VALID_1).when(mSubControllerMock).getSlotIndex(
-                eq(SUB_ID_PHONE_1));
-            doReturn(VALID_SLOT_INDEX_VALID_2).when(mSubControllerMock).getSlotIndex(
-                eq(SUB_ID_PHONE_2));
             doReturn(VALID_SLOT_INDEX_VALID_1).when(mSubscriptionManagerService).getSlotIndex(
                     eq(SUB_ID_PHONE_1));
             doReturn(VALID_SLOT_INDEX_VALID_2).when(mSubscriptionManagerService).getSlotIndex(
@@ -455,7 +492,7 @@
         processAllMessages();
 
         replaceInstance(ShortNumberInfo.class, "INSTANCE", null, mShortNumberInfo);
-        mEmergencyNumberTrackerMock.isEmergencyNumber(NON_3GPP_EMERGENCY_TEST_NUMBER, true);
+        mEmergencyNumberTrackerMock.isEmergencyNumber(NON_3GPP_EMERGENCY_TEST_NUMBER);
 
         //verify we do not use ShortNumber xml
         verify(mShortNumberInfo, never()).isEmergencyNumber(anyString(), anyString());
@@ -495,38 +532,236 @@
     }
 
     /**
-     * In 1.3 or less HAL. we should not use database number.
-     */
-    @Test
-    public void testUsingEmergencyNumberDatabaseWheneverHal_1_3() {
-        doReturn(new HalVersion(1, 3)).when(mPhone).getHalVersion();
-
-        sendEmergencyNumberPrefix(mEmergencyNumberTrackerMock);
-        mEmergencyNumberTrackerMock.updateEmergencyCountryIsoAllPhones("us");
-        processAllMessages();
-
-        boolean hasDatabaseNumber = false;
-        for (EmergencyNumber number : mEmergencyNumberTrackerMock.getEmergencyNumberList()) {
-            if (number.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE)) {
-                hasDatabaseNumber = true;
-                break;
-            }
-        }
-        assertFalse(hasDatabaseNumber);
-    }
-
-    /**
      * In 1.4 or above HAL, we should use database number.
      */
     @Test
     public void testUsingEmergencyNumberDatabaseWheneverHal_1_4() {
-        doReturn(new HalVersion(1, 4)).when(mPhone).getHalVersion();
+        doReturn(mMockContext).when(mPhone).getContext();
+        doReturn(mContext.getAssets()).when(mMockContext).getAssets();
+        doReturn(mResources).when(mMockContext).getResources();
+        doReturn(true).when(mResources).getBoolean(
+                com.android.internal.R.bool.ignore_emergency_number_routing_from_db);
 
-        sendEmergencyNumberPrefix(mEmergencyNumberTrackerMock);
-        mEmergencyNumberTrackerMock.updateEmergencyCountryIsoAllPhones("us");
+        EmergencyNumberTracker emergencyNumberTrackerMock = new EmergencyNumberTracker(
+                mPhone, mSimulatedCommands);
+        emergencyNumberTrackerMock.sendMessage(
+                emergencyNumberTrackerMock.obtainMessage(
+                        1 /* EVENT_UNSOL_EMERGENCY_NUMBER_LIST */,
+                        new AsyncResult(null, mEmergencyNumberListTestSample, null)));
+        sendEmergencyNumberPrefix(emergencyNumberTrackerMock);
+        emergencyNumberTrackerMock.updateEmergencyCountryIsoAllPhones("us");
         processAllMessages();
+        /* case 1: check DB number exist or not */
         assertTrue(hasDbEmergencyNumber(CONFIG_EMERGENCY_NUMBER,
-                mEmergencyNumberTrackerMock.getEmergencyNumberList()));
+                emergencyNumberTrackerMock.getEmergencyNumberList()));
+
+        /* case 2: since ignore_emergency_routing_from_db is true. check for all DB numbers with
+        routing value as unknown by ignoring DB value */
+        List<EmergencyNumber> completeEmergencyNumberList = new ArrayList<>();
+        EmergencyNumber emergencyNumber = new EmergencyNumber(
+                "888", CONFIG_EMERGENCY_NUMBER_COUNTRY,
+                "", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES,
+                CONFIG_EMERGENCY_NUMBER_SERVICE_URNS,
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+        completeEmergencyNumberList.add(emergencyNumber);
+
+        emergencyNumber = new EmergencyNumber(
+                "54321", CONFIG_EMERGENCY_NUMBER_COUNTRY,
+                "", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES,
+                CONFIG_EMERGENCY_NUMBER_SERVICE_URNS,
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+        completeEmergencyNumberList.add(emergencyNumber);
+
+        emergencyNumber = new EmergencyNumber(
+                "654321", CONFIG_EMERGENCY_NUMBER_COUNTRY,
+                "", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES,
+                CONFIG_EMERGENCY_NUMBER_SERVICE_URNS,
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+        completeEmergencyNumberList.add(emergencyNumber);
+
+        emergencyNumber = new EmergencyNumber(
+                "7654321", CONFIG_EMERGENCY_NUMBER_COUNTRY,
+                "", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES,
+                CONFIG_EMERGENCY_NUMBER_SERVICE_URNS,
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+        completeEmergencyNumberList.add(emergencyNumber);
+
+        assertTrue(hasDbEmergencyNumbers(completeEmergencyNumberList,
+                emergencyNumberTrackerMock.getEmergencyNumberList()));
+
+        /* case 3: check the routing type of merged duplicate numbers
+            between DB number and radio list. */
+        EmergencyNumber duplicateEmergencyNumber = new EmergencyNumber(
+                CONFIG_EMERGENCY_DUPLICATE_NUMBER, CONFIG_EMERGENCY_NUMBER_COUNTRY,
+                "", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES,
+                CONFIG_EMERGENCY_NUMBER_SERVICE_URNS,
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE
+                | EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+        assertTrue(hasDbEmergencyNumber(duplicateEmergencyNumber,
+                emergencyNumberTrackerMock.getEmergencyNumberList()));
+    }
+
+    @Test
+    public void testUsingEmergencyNumberDatabaseWithRouting() {
+        doReturn(mMockContext).when(mPhone).getContext();
+        doReturn(mContext.getAssets()).when(mMockContext).getAssets();
+        doReturn(mResources).when(mMockContext).getResources();
+        doReturn("05").when(mCellIdentity).getMncString();
+        doReturn(false).when(mResources).getBoolean(
+                com.android.internal.R.bool.ignore_emergency_number_routing_from_db);
+
+        EmergencyNumberTracker emergencyNumberTrackerMock = new EmergencyNumberTracker(
+                mPhone, mSimulatedCommands);
+        emergencyNumberTrackerMock.sendMessage(
+                emergencyNumberTrackerMock.obtainMessage(
+                        1 /* EVENT_UNSOL_EMERGENCY_NUMBER_LIST */,
+                        new AsyncResult(null, mEmergencyNumberListTestSample, null)));
+        sendEmergencyNumberPrefix(emergencyNumberTrackerMock);
+        emergencyNumberTrackerMock.updateEmergencyCountryIsoAllPhones("us");
+        processAllMessages();
+
+        // case 1: check DB number with normal routing true and for mnc 05
+        EmergencyNumber emergencyNumber = new EmergencyNumber(
+                CONFIG_EMERGENCY_NUMBER_ADDRESS, CONFIG_EMERGENCY_NUMBER_COUNTRY,
+                    "05", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES,
+                            CONFIG_EMERGENCY_NUMBER_SERVICE_URNS,
+                                    EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+                                            EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
+
+        assertTrue(hasDbEmergencyNumber(emergencyNumber,
+                emergencyNumberTrackerMock.getEmergencyNumberList()));
+
+        // case 2: check DB number with normal routing true in multiple mnc 05, 45, 47
+        emergencyNumber = new EmergencyNumber(
+                "888", CONFIG_EMERGENCY_NUMBER_COUNTRY,
+                    "05", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES,
+                            CONFIG_EMERGENCY_NUMBER_SERVICE_URNS,
+                                    EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+                                            EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
+        assertTrue(hasDbEmergencyNumber(emergencyNumber,
+                emergencyNumberTrackerMock.getEmergencyNumberList()));
+
+        doReturn("47").when(mCellIdentity).getMncString();
+        emergencyNumber = new EmergencyNumber(
+                "888", CONFIG_EMERGENCY_NUMBER_COUNTRY,
+                    "47", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES,
+                        CONFIG_EMERGENCY_NUMBER_SERVICE_URNS,
+                            EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+                                EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
+        assertTrue(hasDbEmergencyNumber(emergencyNumber,
+                emergencyNumberTrackerMock.getEmergencyNumberList()));
+
+        emergencyNumber = new EmergencyNumber(
+                CONFIG_EMERGENCY_NUMBER_ADDRESS, CONFIG_EMERGENCY_NUMBER_COUNTRY,
+                    "", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES,
+                            CONFIG_EMERGENCY_NUMBER_SERVICE_URNS,
+                                    EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+                                            EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY);
+        assertTrue(hasDbEmergencyNumber(emergencyNumber,
+                emergencyNumberTrackerMock.getEmergencyNumberList()));
+
+        /* case 3: check DB number with normal routing false and for mnc 05,
+            but current cell identity is 04 */
+        doReturn("04").when(mCellIdentity).getMncString();
+        emergencyNumber = new EmergencyNumber(
+                CONFIG_EMERGENCY_NUMBER_ADDRESS, CONFIG_EMERGENCY_NUMBER_COUNTRY,
+                    "", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES,
+                            CONFIG_EMERGENCY_NUMBER_SERVICE_URNS,
+                                    EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+                                            EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY);
+        assertTrue(hasDbEmergencyNumber(emergencyNumber,
+                emergencyNumberTrackerMock.getEmergencyNumberList()));
+
+        // case 4: check DB number with normal routing false
+        emergencyNumber = new EmergencyNumber(
+                "654321", CONFIG_EMERGENCY_NUMBER_COUNTRY,
+                    "", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES,
+                            CONFIG_EMERGENCY_NUMBER_SERVICE_URNS,
+                                    EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+                                            EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY);
+        assertTrue(hasDbEmergencyNumber(emergencyNumber,
+                emergencyNumberTrackerMock.getEmergencyNumberList()));
+
+        // case 5: check DB number with normal routing true & empty mnc
+        emergencyNumber = new EmergencyNumber(
+                "7654321", CONFIG_EMERGENCY_NUMBER_COUNTRY,
+                    "", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES,
+                            CONFIG_EMERGENCY_NUMBER_SERVICE_URNS,
+                                    EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+                                            EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
+        assertTrue(hasDbEmergencyNumber(emergencyNumber,
+                emergencyNumberTrackerMock.getEmergencyNumberList()));
+
+        /* case 6: check DB number with normal routing true & empty mnc. But same number exist
+            in radio list. In merge DB routing should be used */
+        emergencyNumber = new EmergencyNumber(
+                CONFIG_EMERGENCY_DUPLICATE_NUMBER, CONFIG_EMERGENCY_NUMBER_COUNTRY,
+                "", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES,
+                CONFIG_EMERGENCY_NUMBER_SERVICE_URNS,
+                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE
+                | EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
+
+        assertTrue(hasDbEmergencyNumber(emergencyNumber,
+                emergencyNumberTrackerMock.getEmergencyNumberList()));
+    }
+
+    @Test
+    public void testUsingEmergencyNumberDatabaseWithRoutingInOOS() {
+        doReturn(mMockContext).when(mPhone).getContext();
+        doReturn(mContext.getAssets()).when(mMockContext).getAssets();
+        doReturn(mResources).when(mMockContext).getResources();
+        doReturn(false).when(mResources).getBoolean(
+                com.android.internal.R.bool.ignore_emergency_number_routing_from_db);
+
+        EmergencyNumberTracker emergencyNumberTrackerMock = new EmergencyNumberTracker(
+                mPhone, mSimulatedCommands);
+        emergencyNumberTrackerMock.sendMessage(
+                emergencyNumberTrackerMock.obtainMessage(
+                        1 /* EVENT_UNSOL_EMERGENCY_NUMBER_LIST */,
+                        new AsyncResult(null, mEmergencyNumberListTestSample, null)));
+        sendEmergencyNumberPrefix(emergencyNumberTrackerMock);
+        emergencyNumberTrackerMock.updateEmergencyCountryIsoAllPhones("us");
+        processAllMessages();
+
+        // Check routing when cellidentity is null, which is oos
+        doReturn(null).when(mPhone).getCurrentCellIdentity();
+        EmergencyNumber emergencyNumber = new EmergencyNumber(
+                CONFIG_EMERGENCY_NUMBER_ADDRESS, CONFIG_EMERGENCY_NUMBER_COUNTRY,
+                    "", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES,
+                            CONFIG_EMERGENCY_NUMBER_SERVICE_URNS,
+                                    EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+                                            EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
+        assertTrue(hasDbEmergencyNumber(emergencyNumber,
+                emergencyNumberTrackerMock.getEmergencyNumberList()));
+
+        // Check routing when cellidentity is 04, which is not part of normal routing mncs
+        doReturn(mCellIdentity).when(mPhone).getCurrentCellIdentity();
+        doReturn("04").when(mCellIdentity).getMncString();
+        emergencyNumber = new EmergencyNumber(
+                CONFIG_EMERGENCY_NUMBER_ADDRESS, CONFIG_EMERGENCY_NUMBER_COUNTRY,
+                    "", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES,
+                            CONFIG_EMERGENCY_NUMBER_SERVICE_URNS,
+                                    EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+                                            EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY);
+        assertTrue(hasDbEmergencyNumber(emergencyNumber,
+                emergencyNumberTrackerMock.getEmergencyNumberList()));
+
+        // Check routing when cellidentity is 05, which is part of normal routing mncs
+        doReturn("05").when(mCellIdentity).getMncString();
+        emergencyNumber = new EmergencyNumber(
+                CONFIG_EMERGENCY_NUMBER_ADDRESS, CONFIG_EMERGENCY_NUMBER_COUNTRY,
+                    "05", CONFIG_EMERGENCY_NUMBER_SERVICE_CATEGORIES,
+                            CONFIG_EMERGENCY_NUMBER_SERVICE_URNS,
+                                    EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE,
+                                            EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
+        assertTrue(hasDbEmergencyNumber(emergencyNumber,
+                emergencyNumberTrackerMock.getEmergencyNumberList()));
     }
 
     /**
@@ -534,9 +769,6 @@
      */
     @Test
     public void testOtaEmergencyNumberDatabase() {
-        // Set up the Hal version as 1.4 to apply emergency number database
-        doReturn(new HalVersion(1, 4)).when(mPhone).getHalVersion();
-
         sendEmergencyNumberPrefix(mEmergencyNumberTrackerMock);
         mEmergencyNumberTrackerMock.updateEmergencyCountryIsoAllPhones("");
         processAllMessages();
@@ -597,4 +829,42 @@
 
         assertEquals(resultToVerify, resultFromRadio);
     }
+
+    @Test
+    public void testOverridingEmergencyNumberPrefixCarrierConfig() throws Exception {
+        // Capture CarrierConfigChangeListener to emulate the carrier config change notification
+        doReturn(mMockContext).when(mPhone).getContext();
+        doReturn(Context.CARRIER_CONFIG_SERVICE)
+                .when(mMockContext)
+                .getSystemService(CarrierConfigManager.class);
+        doReturn(mCarrierConfigManagerMock)
+                .when(mMockContext)
+                .getSystemService(eq(Context.CARRIER_CONFIG_SERVICE));
+        ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> listenerArgumentCaptor =
+                ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
+        EmergencyNumberTracker localEmergencyNumberTracker =
+                new EmergencyNumberTracker(mPhone, mSimulatedCommands);
+        verify(mCarrierConfigManagerMock)
+                .registerCarrierConfigChangeListener(any(), listenerArgumentCaptor.capture());
+        CarrierConfigManager.CarrierConfigChangeListener carrierConfigChangeListener =
+                listenerArgumentCaptor.getAllValues().get(0);
+
+        assertFalse(localEmergencyNumberTracker.isEmergencyNumber("*272911"));
+
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putStringArray(
+                CarrierConfigManager.KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY,
+                new String[] {"*272"});
+        doReturn(bundle)
+                .when(mCarrierConfigManagerMock)
+                .getConfigForSubId(eq(SUB_ID_PHONE_1), any());
+        carrierConfigChangeListener.onCarrierConfigChanged(
+                mPhone.getPhoneId(),
+                mPhone.getSubId(),
+                TelephonyManager.UNKNOWN_CARRIER_ID,
+                TelephonyManager.UNKNOWN_CARRIER_ID);
+        processAllMessages();
+
+        assertTrue(localEmergencyNumberTracker.isEmergencyNumber("*272911"));
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java
new file mode 100644
index 0000000..2a8e4e2
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java
@@ -0,0 +1,1615 @@
+/*
+ * Copyright (C) 2022 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.emergency;
+
+import static android.telephony.AccessNetworkConstants.AccessNetworkType.EUTRAN;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_CS_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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThrows;
+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.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.telephony.CarrierConfigManager;
+import android.telephony.DisconnectCause;
+import android.telephony.EmergencyRegResult;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+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 com.android.internal.telephony.data.PhoneSwitcher;
+
+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.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
+
+/**
+ * Unit tests for EmergencyStateTracker
+ */
+@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(
+            EUTRAN, REGISTRATION_STATE_HOME, DOMAIN_CS_PS, true, true, 0, 1, "001", "01", "US");
+
+    @Mock EmergencyStateTracker.PhoneFactoryProxy mPhoneFactoryProxy;
+    @Mock EmergencyStateTracker.PhoneSwitcherProxy mPhoneSwitcherProxy;
+    @Mock EmergencyStateTracker.TelephonyManagerProxy mTelephonyManagerProxy;
+    @Mock PhoneSwitcher mPhoneSwitcher;
+    @Mock RadioOnHelper mRadioOnHelper;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    @SmallTest
+    public void getInstance_notInitializedTillMake() throws IllegalStateException {
+        assertThrows(IllegalStateException.class, () -> {
+            EmergencyStateTracker.getInstance();
+        });
+
+        EmergencyStateTracker.make(mContext, true);
+
+        assertNotNull(EmergencyStateTracker.getInstance());
+    }
+
+    @Test
+    @SmallTest
+    public void getInstance_returnsSameInstance() {
+        EmergencyStateTracker.make(mContext, true);
+        EmergencyStateTracker instance1 = EmergencyStateTracker.getInstance();
+        EmergencyStateTracker instance2 = EmergencyStateTracker.getInstance();
+
+        assertSame(instance1, instance2);
+    }
+
+    /**
+     * 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_turnOnRadioSwitchDdsAndSetEmergencyMode() {
+        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");
+        // Spy is used to capture consumer in delayDialForDdsSwitch
+        EmergencyStateTracker spyEst = spy(emergencyStateTracker);
+        CompletableFuture<Integer> unused = spyEst.startEmergencyCall(testPhone, TEST_CALL_ID,
+                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
+        assertFalse(callback.getValue()
+                .isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE, false));
+        when(mSST.isRadioOn()).thenReturn(true);
+        assertTrue(callback.getValue()
+                .isOkToCall(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
+                .forClass(Consumer.class);
+        verify(spyEst).switchDdsDelayed(eq(testPhone), completeConsumer.capture());
+        verify(mPhoneSwitcher).overrideDefaultDataForEmergency(eq(testPhone.getPhoneId()),
+                eq(150) /* extensionTime */, any());
+        // After dds switch completes successfully, set emergency mode
+        completeConsumer.getValue().accept(true);
+        verify(testPhone).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any());
+    }
+
+    /**
+     * Test that if startEmergencyCall fails to turn on radio, then it's future completes with
+     * DisconnectCause.POWER_OFF.
+     */
+    @Test
+    @SmallTest
+    public void startEmergencyCall_radioOnFails_returnsDisconnectCausePowerOff() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                true /* isSuplDdsSwitchRequiredForEmergencyCall */);
+        // Create test Phones and set radio off
+        Phone testPhone = setupTestPhoneForEmergencyCall(false /* isRoaming */,
+                false /* isRadioOn */);
+
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(testPhone,
+                TEST_CALL_ID, 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));
+        // Verify future completes with DisconnectCause.POWER_OFF if radio not ready
+        CompletableFuture<Void> unused = future.thenAccept((result) -> {
+            assertEquals((Integer) result, (Integer) DisconnectCause.POWER_OFF);
+        });
+        callback.getValue().onComplete(null, false /* isRadioReady */);
+    }
+
+    /**
+     * Test that the EmergencyStateTracker does not perform a DDS switch when the carrier supports
+     * control-plane fallback. Radio is set to on so RadioOnHelper not triggered.
+     */
+    @Test
+    @SmallTest
+    public void startEmergencyCall_cpFallback_noDdsSwitch() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                true /* isSuplDdsSwitchRequiredForEmergencyCall */);
+        // Create test Phones and set radio on
+        Phone testPhone = setupTestPhoneForEmergencyCall(false /* isRoaming */,
+                true /* isRadioOn */);
+        setConfigForDdsSwitch(testPhone, null,
+                CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_FALLBACK, "0");
+
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
+                TEST_CALL_ID, false);
+
+        // Radio already on so shouldn't trigger this
+        verify(mRadioOnHelper, never()).triggerRadioOnAndListen(any(), anyBoolean(), any(),
+                anyBoolean(), eq(0));
+        // Carrier supports control-plane fallback, so no DDS switch
+        verify(mPhoneSwitcher, never()).overrideDefaultDataForEmergency(anyInt(), anyInt(), any());
+    }
+
+    /**
+     * Test that the EmergencyStateTracker does not perform a DDS switch if the non-DDS supports
+     * SUPL.
+     */
+    @Test
+    @SmallTest
+    public void startEmergencyCall_supportsSuplOnNonDds_noDdsSwitch() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                false /* isSuplDdsSwitchRequiredForEmergencyCall */);
+        // Create test Phones
+        Phone testPhone = setupTestPhoneForEmergencyCall(false /* isRoaming */,
+                true /* isRadioOn */);
+        setConfigForDdsSwitch(testPhone, null,
+                CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY, "0");
+
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
+                TEST_CALL_ID, false);
+
+        // non-DDS supports SUPL, so no DDS switch
+        verify(mPhoneSwitcher, never()).overrideDefaultDataForEmergency(anyInt(), anyInt(), any());
+    }
+
+    /**
+     * Test that the EmergencyStateTracker does not perform a DDS switch when the carrier does not
+     * support control-plane fallback while roaming.
+     */
+    @Test
+    @SmallTest
+    public void startEmergencyCall_roaming_noDdsSwitch() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                true /* isSuplDdsSwitchRequiredForEmergencyCall */);
+        // Create test Phones
+        Phone testPhone = setupTestPhoneForEmergencyCall(true /* isRoaming */,
+                true /* isRadioOn */);
+        setConfigForDdsSwitch(testPhone, null,
+                CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY, "0");
+
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
+                TEST_CALL_ID, false);
+
+        // Is roaming, so no DDS switch
+        verify(mPhoneSwitcher, never()).overrideDefaultDataForEmergency(anyInt(), anyInt(), any());
+    }
+
+    /**
+     * Test that the EmergencyStateTracker does perform a DDS switch even though the carrier
+     * supports control-plane fallback and the roaming partner is configured to look like a home
+     * network.
+     */
+    @Test
+    @SmallTest
+    public void startEmergencyCall_roamingCarrierConfig_switchDds() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                true /* isSuplDdsSwitchRequiredForEmergencyCall */);
+        // Create test Phones
+        Phone testPhone = setupTestPhoneForEmergencyCall(false /* isRoaming */,
+                true /* isRadioOn */);
+        // Setup voice roaming scenario
+        String testRoamingOperator = "001001";
+        testPhone.getServiceState().setOperatorName("TestTel", "TestTel", testRoamingOperator);
+        String[] roamingPlmns = new String[] { testRoamingOperator };
+        setConfigForDdsSwitch(testPhone, roamingPlmns,
+                CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_FALLBACK, "0");
+
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
+                TEST_CALL_ID, false);
+
+        // Verify DDS switch
+        verify(mPhoneSwitcher).overrideDefaultDataForEmergency(eq(0) /* phoneId */,
+                eq(0) /* extensionTime */, any());
+    }
+
+    /**
+     * Test that the EmergencyStateTracker does perform a DDS switch even though the carrier
+     * supports control-plane fallback if we are roaming and the roaming partner is configured to
+     * use data plane only SUPL.
+     */
+    @Test
+    @SmallTest
+    public void startEmergencyCall_roamingCarrierConfigWhileRoaming_switchDds() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                true /* isSuplDdsSwitchRequiredForEmergencyCall */);
+        // Create test Phones
+        Phone testPhone = setupTestPhoneForEmergencyCall(true /* isRoaming */,
+                true /* isRadioOn */);
+        // Setup voice roaming scenario
+        String testRoamingOperator = "001001";
+        testPhone.getServiceState().setOperatorName("TestTel", "TestTel", testRoamingOperator);
+        String[] roamingPlmns = new String[] { testRoamingOperator };
+        setConfigForDdsSwitch(testPhone, roamingPlmns,
+                CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_FALLBACK, "0");
+
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
+                TEST_CALL_ID, false);
+
+        // Verify DDS switch
+        verify(mPhoneSwitcher).overrideDefaultDataForEmergency(eq(0) /* phoneId */,
+                eq(0) /* extensionTime */, any());
+    }
+
+    /**
+     * Test that once EmergencyStateTracker handler receives set emergency mode done message it sets
+     * IsInEmergencyCall to true, sets LastEmergencyRegResult and completes future with
+     * DisconnectCause.NOT_DISCONNECTED.
+     */
+    @Test
+    @SmallTest
+    public void setEmergencyModeDone_notifiesListenersAndCompletesFuture() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                true /* isSuplDdsSwitchRequiredForEmergencyCall */);
+        // Create test Phone
+        Phone testPhone = setupTestPhoneForEmergencyCall(true /* isRoaming */,
+                true /* isRadioOn */);
+        setUpAsyncResultForSetEmergencyMode(testPhone, E_REG_RESULT);
+        // Call startEmergencyCall() to set testPhone
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(testPhone,
+                TEST_CALL_ID, false);
+        // Verify future completes with DisconnectCause.NOT_DISCONNECTED
+        CompletableFuture<Void> unused = future.thenAccept((result) -> {
+            assertEquals((Integer) result, (Integer) DisconnectCause.NOT_DISCONNECTED);
+        });
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+        assertTrue(emergencyStateTracker.getEmergencyRegResult().equals(E_REG_RESULT));
+        verify(testPhone).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+    }
+
+    /**
+     * Test that once EmergencyStateTracker handler receives message to exit emergency mode, it sets
+     * IsInEmergencyCall to false.
+     */
+    @Test
+    @SmallTest
+    public void exitEmergencyModeDone_isInEmergencyCallFalse() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                true /* isSuplDdsSwitchRequiredForEmergencyCall */);
+        // Create test Phone
+        Phone testPhone = setupTestPhoneForEmergencyCall(true /* isRoaming */,
+                true /* isRadioOn */);
+        setUpAsyncResultForSetEmergencyMode(testPhone, E_REG_RESULT);
+        setUpAsyncResultForExitEmergencyMode(testPhone);
+        // Call startEmergencyCall() to set testPhone
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
+                TEST_CALL_ID, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+        verify(testPhone).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+
+        emergencyStateTracker.endCall(TEST_CALL_ID);
+        processAllMessages();
+
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+        verify(testPhone).exitEmergencyMode(any(Message.class));
+    }
+
+    /**
+     * Test that onEmergencyCallDomainUpdated updates the domain correctly so ECBM PS domain is
+     * detected.
+     */
+    @Test
+    @SmallTest
+    public void onEmergencyCallDomainUpdated_PsDomain() {
+        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,
+                TEST_CALL_ID, false);
+
+        // Set call to ACTIVE
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        // set domain
+        emergencyStateTracker.onEmergencyCallDomainUpdated(PhoneConstants.PHONE_TYPE_IMS,
+                TEST_CALL_ID);
+        // End call to enter ECM
+        emergencyStateTracker.endCall(TEST_CALL_ID);
+
+        // Make sure CS ECBM is true
+        assertTrue(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInCdmaEcm());
+        assertTrue(emergencyStateTracker.isInImsEcm());
+    }
+
+    /**
+     * Test that onEmergencyCallDomainUpdated updates the domain correctly so ECBM CS domain is
+     * detected.
+     */
+    @Test
+    @SmallTest
+    public void onEmergencyCallDomainUpdated_CsDomain() {
+        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,
+                TEST_CALL_ID, false);
+
+        // Set call to ACTIVE
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        // set domain
+        emergencyStateTracker.onEmergencyCallDomainUpdated(PhoneConstants.PHONE_TYPE_CDMA,
+                TEST_CALL_ID);
+        // End call to enter ECM
+        emergencyStateTracker.endCall(TEST_CALL_ID);
+
+        // Make sure IMS ECBM is true
+        assertTrue(emergencyStateTracker.isInEcm());
+        assertTrue(emergencyStateTracker.isInCdmaEcm());
+        assertFalse(emergencyStateTracker.isInImsEcm());
+    }
+
+    /**
+     * Ensure that if for some reason we enter ECBM for CS domain and the Phone type is GSM,
+     * isInCdmaEcm returns false.
+     */
+    @Test
+    @SmallTest
+    public void onEmergencyCallDomainUpdated_CsDomain_Gsm() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        // Create test Phones
+        Phone testPhone = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        // For some reason the Phone is reporting GSM instead of CDMA.
+        doReturn(PhoneConstants.PHONE_TYPE_GSM).when(testPhone).getPhoneType();
+        // Call startEmergencyCall() to set testPhone
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
+                TEST_CALL_ID, false);
+
+        // Set call to ACTIVE
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        // set domain
+        emergencyStateTracker.onEmergencyCallDomainUpdated(PhoneConstants.PHONE_TYPE_CDMA,
+                TEST_CALL_ID);
+        // End call to enter ECM
+        emergencyStateTracker.endCall(TEST_CALL_ID);
+
+        assertTrue(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInCdmaEcm());
+        assertFalse(emergencyStateTracker.isInImsEcm());
+    }
+
+    /**
+     * Test that onEmergencyTransportChanged sets the new emergency mode.
+     */
+    @Test
+    @SmallTest
+    public void onEmergencyTransportChanged_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,
+                TEST_CALL_ID, false);
+
+        emergencyStateTracker.onEmergencyTransportChanged(
+                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.
+     */
+    @Test
+    @SmallTest
+    public void endCall_callWasActive_enterEcm() {
+        // Setup EmergencyStateTracker
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        // Create test Phone
+        Phone testPhone = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        // Start emergency call then enter ECM
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
+                TEST_CALL_ID, false);
+        // Set call to ACTIVE
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        // Set ecm as supported
+        setEcmSupportedConfig(testPhone, true);
+
+        assertFalse(emergencyStateTracker.isInEcm());
+
+        emergencyStateTracker.endCall(TEST_CALL_ID);
+
+        assertTrue(emergencyStateTracker.isInEcm());
+        // Verify intents are sent that ECM is entered
+        ArgumentCaptor<Intent> ecmStateIntent = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).sendStickyBroadcastAsUser(ecmStateIntent.capture(), eq(UserHandle.ALL));
+        assertTrue(ecmStateIntent.getValue()
+                .getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, true));
+        // Verify emergency callback mode set on modem
+        verify(testPhone).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any());
+    }
+
+    /**
+     * Test that after endCall() is called, EmergencyStateTracker will not enter ECM if the call was
+     * not ACTIVE.
+     */
+    @Test
+    @SmallTest
+    public void endCall_callNotActive_noEcm() {
+        // Setup EmergencyStateTracker
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        // Create test Phone
+        Phone testPhone = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        // Start emergency call then enter ECM
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
+                TEST_CALL_ID, false);
+        // Call does not reach ACTIVE
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.IDLE, TEST_CALL_ID);
+        // Set ecm as supported
+        setEcmSupportedConfig(testPhone, /* ecmSupported= */ true);
+
+        emergencyStateTracker.endCall(TEST_CALL_ID);
+
+        assertFalse(emergencyStateTracker.isInEcm());
+    }
+
+    /**
+     * Test that once endCall() is called and we enter ECM, then we exit ECM after the specified
+     * timeout.
+     */
+    @Test
+    @SmallTest
+    public void endCall_entersEcm_thenExitsEcmAfterTimeout() {
+        // 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);
+        // Set ecm as supported
+        setEcmSupportedConfig(testPhone, /* ecmSupported= */ true);
+
+        processAllMessages();
+
+        emergencyStateTracker.endCall(TEST_CALL_ID);
+
+        assertTrue(emergencyStateTracker.isInEcm());
+
+        processAllFutureMessages();
+
+        // Verify exitEmergencyMode() is called after timeout
+        verify(testPhone).exitEmergencyMode(any(Message.class));
+        assertFalse(emergencyStateTracker.isInEmergencyMode());
+    }
+
+    /**
+     * Test that after exitEmergencyCallbackMode() is called, the correct intents are sent and
+     * emergency mode is exited on the modem.
+     */
+    @Test
+    @SmallTest
+    public void exitEmergencyCallbackMode_sendsCorrectIntentsAndExitsEmergencyMode() {
+        // Setup EmergencyStateTracker
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        // Create test Phone
+        Phone testPhone = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(testPhone, E_REG_RESULT);
+        setUpAsyncResultForExitEmergencyMode(testPhone);
+        // Start emergency call then enter ECM
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
+                TEST_CALL_ID, false);
+        processAllMessages();
+        // 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);
+        // End call to enter ECM
+        emergencyStateTracker.endCall(TEST_CALL_ID);
+        processAllMessages();
+
+        // verify ecbm states are correct
+        assertTrue(emergencyStateTracker.isInEcm());
+        assertTrue(emergencyStateTracker.isInImsEcm());
+        assertFalse(emergencyStateTracker.isInCdmaEcm());
+
+        emergencyStateTracker.exitEmergencyCallbackMode();
+        processAllFutureMessages();
+
+        // Ensure ECBM states are all correctly false after we exit.
+        assertFalse(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInImsEcm());
+        assertFalse(emergencyStateTracker.isInCdmaEcm());
+        // Intents sent for ECM: one for entering ECM and another for exiting
+        ArgumentCaptor<Intent> ecmStateIntent = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, times(2))
+                .sendStickyBroadcastAsUser(ecmStateIntent.capture(), eq(UserHandle.ALL));
+        List<Intent> capturedIntents = ecmStateIntent.getAllValues();
+        assertTrue(capturedIntents.get(0)
+                .getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false));
+        assertFalse(capturedIntents.get(1)
+                .getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false));
+        // Verify exitEmergencyMode() is called only once
+        verify(testPhone).exitEmergencyMode(any(Message.class));
+    }
+
+    @Test
+    @SmallTest
+    public void testOnEmergencyTransportChangedUsingDifferentThread() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
+                TEST_CALL_ID, false);
+
+        Handler handler = new Handler(Looper.getMainLooper());
+        handler.post(() -> {
+            emergencyStateTracker.onEmergencyTransportChanged(
+                    EmergencyStateTracker.EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WWAN);
+        });
+        processAllMessages();
+
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any());
+    }
+
+    @Test
+    @SmallTest
+    public void testStartEmergencyCallWithTestEmergencyNumber() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone0,
+                TEST_CALL_ID, 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));
+    }
+
+    @Test
+    @SmallTest
+    public void testStartEmergencyCallDuringActiveCall() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
+        // First active call
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
+                TEST_CALL_ID, 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);
+
+        // Second starting call
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone0,
+                TEST_CALL_ID_2, false);
+
+        // Returns DisconnectCause#NOT_DISCONNECTED immediately.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+    }
+
+    @Test
+    @SmallTest
+    public void testStartEmergencyCallInEcm() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
+        setEcmSupportedConfig(phone0, true);
+
+        // First active call
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
+                TEST_CALL_ID, 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);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEcm());
+
+        // Second emergency call started.
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone0,
+                TEST_CALL_ID_2, false);
+
+        // Returns DisconnectCause#NOT_DISCONNECTED immediately.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+    }
+
+    @Test
+    @SmallTest
+    public void testStartEmergencyCallUsingDifferenPhone() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
+
+        // First emergency call
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
+                TEST_CALL_ID, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+
+        // Second emergency call
+        Phone phone1 = getPhone(1);
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone1,
+                TEST_CALL_ID_2, false);
+
+        // Returns DisconnectCause#ERROR_UNSPECIFIED immediately.
+        assertEquals(future.getNow(DisconnectCause.NOT_DISCONNECTED),
+                Integer.valueOf(DisconnectCause.ERROR_UNSPECIFIED));
+    }
+
+    @Test
+    @SmallTest
+    public void testEndCallInEcm() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
+        setEcmSupportedConfig(phone0, true);
+
+        // First active call
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone0,
+                TEST_CALL_ID, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.endCall(TEST_CALL_ID);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+
+        // Second emergency call started.
+        future = emergencyStateTracker.startEmergencyCall(phone0, TEST_CALL_ID_2, false);
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        // Returns DisconnectCause#NOT_DISCONNECTED immediately.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+
+        emergencyStateTracker.onEmergencyTransportChanged(
+                EmergencyStateTracker.EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WLAN);
+        emergencyStateTracker.endCall(TEST_CALL_ID_2);
+        processAllMessages();
+
+        // At this time, ECM is still running so still in ECM.
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WLAN), any(Message.class));
+        verify(phone0, times(2)).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
+        verify(phone0, never()).exitEmergencyMode(any(Message.class));
+    }
+
+    @Test
+    @SmallTest
+    public void testStartEmergencySms() {
+        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.getEmergencyRegResult().equals(E_REG_RESULT));
+        // Expect: DisconnectCause#NOT_DISCONNECTED.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+    }
+
+    @Test
+    @SmallTest
+    public void testEndSms() {
+        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.getEmergencyRegResult().equals(E_REG_RESULT));
+        // Expect: DisconnectCause#NOT_DISCONNECTED.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+
+        emergencyStateTracker.endSms(TEST_SMS_ID, getTestEmergencyNumber());
+
+        verify(phone0).exitEmergencyMode(any(Message.class));
+        assertFalse(emergencyStateTracker.isInEmergencyMode());
+    }
+
+    @Test
+    @SmallTest
+    public void testStartEmergencySmsWithTransportChange() {
+        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));
+        // Expect: DisconnectCause#NOT_DISCONNECTED.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+
+        emergencyStateTracker.onEmergencyTransportChanged(
+                EmergencyStateTracker.EMERGENCY_TYPE_SMS, MODE_EMERGENCY_WLAN);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.getEmergencyRegResult().equals(E_REG_RESULT));
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WLAN), any(Message.class));
+    }
+
+    @Test
+    @SmallTest
+    public void testStartEmergencySmsWithTestEmergencyNumber() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ false,
+                /* isRadioOn= */ true);
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
+                TEST_SMS_ID, true);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        // Expect: DisconnectCause#NOT_DISCONNECTED.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+        verify(phone0, never()).setEmergencyMode(anyInt(), any(Message.class));
+    }
+
+    @Test
+    @SmallTest
+    public void testStartEmergencySmsWhileSmsBeingSent() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ false,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencySms(phone0,
+                TEST_SMS_ID, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+
+        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));
+    }
+
+    @Test
+    @SmallTest
+    public void testStartEmergencySmsWhileEmergencyModeBeingSet() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ false,
+                /* isRadioOn= */ true);
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencySms(phone0,
+                TEST_SMS_ID, false);
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
+                TEST_SMS_ID_2, false);
+
+        // Returns DisconnectCause#ERROR_UNSPECIFIED immediately.
+        assertEquals(future.getNow(DisconnectCause.NOT_DISCONNECTED),
+                Integer.valueOf(DisconnectCause.ERROR_UNSPECIFIED));
+    }
+
+    @Test
+    @SmallTest
+    public void testStartEmergencySmsWhileEcmInProgress() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
+        setEcmSupportedConfig(phone0, true);
+        // Emergency call is ended and the emergency callback is entered.
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
+                TEST_CALL_ID, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+
+        emergencyStateTracker.onEmergencyCallStateChanged(Call.State.ACTIVE, TEST_CALL_ID);
+        emergencyStateTracker.endCall(TEST_CALL_ID);
+
+        assertTrue(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+
+        // Emergency SMS is being started.
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
+                TEST_SMS_ID, false);
+
+        assertFalse(future.isDone());
+
+        // Completes the emergency mode setting - MODE_EMERGENCY_CALLBACK
+        processAllMessages();
+
+        verify(phone0, times(2)).setEmergencyMode(anyInt(), any(Message.class));
+        // Expect: DisconnectCause#NOT_DISCONNECTED.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+    }
+
+    @Test
+    @SmallTest
+    public void testStartEmergencySmsUsingDifferentPhone() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ false,
+                /* isRadioOn= */ true);
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencySms(phone0,
+                TEST_SMS_ID, false);
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+
+        Phone phone1 = getPhone(1);
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone1,
+                TEST_SMS_ID_2, false);
+
+        // Returns DisconnectCause#ERROR_UNSPECIFIED immediately.
+        assertEquals(future.getNow(DisconnectCause.NOT_DISCONNECTED),
+                Integer.valueOf(DisconnectCause.ERROR_UNSPECIFIED));
+    }
+
+    @Test
+    @SmallTest
+    public void testStartEmergencyCallActiveAndSmsOnSamePhone() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
+        // Emergency call is in active.
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
+                TEST_CALL_ID, 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);
+
+        // 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));
+    }
+
+    @Test
+    @SmallTest
+    public void testStartEmergencyCallInProgressAndSmsOnSamePhone() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        // Emergency call is in progress.
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
+                TEST_CALL_ID, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+        ArgumentCaptor<Message> msgCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), msgCaptor.capture());
+
+        // Emergency SMS is being started.
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
+                TEST_SMS_ID, false);
+
+        assertFalse(future.isDone());
+
+        Message msg = msgCaptor.getValue();
+        AsyncResult.forMessage(msg, E_REG_RESULT, null);
+        msg.sendToTarget();
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+        // Returns DisconnectCause#NOT_DISCONNECTED immediately.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+    }
+
+    @Test
+    @SmallTest
+    public void testStartEmergencySmsActiveAndCallOnSamePhone() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
+        setUpAsyncResultForExitEmergencyMode(phone0);
+        // Emergency SMS is in active.
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
+                TEST_SMS_ID, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        // Expect: DisconnectCause#NOT_DISCONNECTED.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+
+        // Emergency call is being started.
+        future = emergencyStateTracker.startEmergencyCall(phone0, TEST_CALL_ID, false);
+        processAllMessages();
+
+        verify(phone0).exitEmergencyMode(any(Message.class));
+        verify(phone0, times(2)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+        // Expect: DisconnectCause#NOT_DISCONNECTED.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+    }
+
+    @Test
+    @SmallTest
+    public void testStartEmergencySmsInProgressAndCallOnSamePhone() {
+        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,
+                TEST_CALL_ID, false);
+
+        assertFalse(smsFuture.isDone());
+        assertFalse(callFuture.isDone());
+
+        // Response message for setEmergencyMode by SMS.
+        Message msg = smsCaptor.getValue();
+        AsyncResult.forMessage(msg, E_REG_RESULT, null);
+        msg.sendToTarget();
+        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));
+        ArgumentCaptor<Message> callCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(phone0, times(2)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), callCaptor.capture());
+
+        // Response message for setEmergencyMode by call.
+        msg = callCaptor.getAllValues().get(1);
+        AsyncResult.forMessage(msg, E_REG_RESULT, null);
+        msg.sendToTarget();
+        processAllMessages();
+
+        // Expect: DisconnectCause#NOT_DISCONNECTED
+        assertEquals(smsFuture.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+        // 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);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
+        // Emergency call is in active.
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
+                TEST_CALL_ID, 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);
+
+        // Emergency SMS is being started using the different phone.
+        Phone phone1 = getPhone(1);
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone1,
+                TEST_SMS_ID, false);
+
+        // Returns DisconnectCause#ERROR_UNSPECIFIED immediately.
+        assertEquals(future.getNow(DisconnectCause.NOT_DISCONNECTED),
+                Integer.valueOf(DisconnectCause.ERROR_UNSPECIFIED));
+    }
+
+    @Test
+    @SmallTest
+    public void testStartEmergencySmsActiveAndCallOnDifferentPhone() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
+        setUpAsyncResultForExitEmergencyMode(phone0);
+        // Emergency SMS is in active.
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
+                TEST_SMS_ID, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        // Expect: DisconnectCause#NOT_DISCONNECTED.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+
+        // 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);
+        processAllMessages();
+
+        verify(phone0).exitEmergencyMode(any(Message.class));
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        verify(phone1).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+        // Expect: DisconnectCause#NOT_DISCONNECTED.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+    }
+
+    @Test
+    @SmallTest
+    public void testStartEmergencySmsInProgressAndCallOnDifferentPhone() {
+        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 using the different phone.
+        Phone phone1 = getPhone(1);
+        CompletableFuture<Integer> callFuture = emergencyStateTracker.startEmergencyCall(phone1,
+                TEST_CALL_ID, false);
+
+        assertFalse(smsFuture.isDone());
+        assertFalse(callFuture.isDone());
+
+        // Response message for setEmergencyMode by SMS.
+        Message msg = smsCaptor.getValue();
+        AsyncResult.forMessage(msg, E_REG_RESULT, null);
+        msg.sendToTarget();
+        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));
+        ArgumentCaptor<Message> callCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(phone1).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), callCaptor.capture());
+
+        // Response message for setEmergencyMode by call.
+        msg = callCaptor.getValue();
+        AsyncResult.forMessage(msg, E_REG_RESULT, null);
+        msg.sendToTarget();
+        processAllMessages();
+
+        // Expect: DisconnectCause#OUTGOING_EMERGENCY_CALL_PLACED
+        assertEquals(smsFuture.getNow(DisconnectCause.NOT_DISCONNECTED),
+                Integer.valueOf(DisconnectCause.OUTGOING_EMERGENCY_CALL_PLACED));
+        // Expect: DisconnectCause#NOT_DISCONNECTED.
+        assertEquals(callFuture.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+    }
+
+    @Test
+    @SmallTest
+    public void testExitEmergencyModeCallAndSmsOnSamePhone() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
+        setUpAsyncResultForExitEmergencyMode(phone0);
+        setEcmSupportedConfig(phone0, false);
+        // Emergency call is in active.
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
+                TEST_CALL_ID, 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);
+
+        // 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(TEST_CALL_ID);
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+
+        emergencyStateTracker.endSms(TEST_SMS_ID, getTestEmergencyNumber());
+        processAllMessages();
+
+        assertFalse(emergencyStateTracker.isInEmergencyMode());
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+        verify(phone0).exitEmergencyMode(any(Message.class));
+    }
+
+    @Test
+    @SmallTest
+    public void testExitEmergencyModeSmsAndCallOnSamePhone() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
+        setUpAsyncResultForExitEmergencyMode(phone0);
+        setEcmSupportedConfig(phone0, false);
+        // Emergency call is in active.
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
+                TEST_CALL_ID, 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);
+
+        // 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.endSms(TEST_SMS_ID, getTestEmergencyNumber());
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+
+        emergencyStateTracker.endCall(TEST_CALL_ID);
+        processAllMessages();
+
+        assertFalse(emergencyStateTracker.isInEmergencyMode());
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+        verify(phone0).exitEmergencyMode(any(Message.class));
+    }
+
+    @Test
+    @SmallTest
+    public void testExitEmergencyModeCallAndSmsOnSamePhoneWhenEcmSupported() {
+        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,
+                TEST_CALL_ID, 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);
+
+        // 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(TEST_CALL_ID);
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+
+        emergencyStateTracker.endSms(TEST_SMS_ID, getTestEmergencyNumber());
+        processAllMessages();
+
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+
+        // ECM timeout.
+        processAllFutureMessages();
+
+        assertFalse(emergencyStateTracker.isInEmergencyMode());
+        assertFalse(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+        verify(phone0).exitEmergencyMode(any(Message.class));
+    }
+
+    @Test
+    @SmallTest
+    public void testExitEmergencyModeCallAndSmsOnSamePhoneWhenEcmSupportedAndModeChanged() {
+        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,
+                TEST_CALL_ID, 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);
+
+        // 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(TEST_CALL_ID);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+
+        emergencyStateTracker.onEmergencyTransportChanged(
+                EmergencyStateTracker.EMERGENCY_TYPE_SMS, MODE_EMERGENCY_WWAN);
+        emergencyStateTracker.endSms(TEST_SMS_ID, getTestEmergencyNumber());
+        processAllMessages();
+
+        // Enter emergency callback mode and emergency mode changed by SMS end.
+        verify(phone0, times(2)).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+
+        // ECM timeout.
+        processAllFutureMessages();
+
+        assertFalse(emergencyStateTracker.isInEmergencyMode());
+        assertFalse(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+        verify(phone0).exitEmergencyMode(any(Message.class));
+    }
+
+    @Test
+    @SmallTest
+    public void testExitEmergencyModeSmsAndCallOnSamePhoneWhenEcmSupported() {
+        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,
+                TEST_CALL_ID, 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);
+
+        // 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.endSms(TEST_SMS_ID, getTestEmergencyNumber());
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+
+        emergencyStateTracker.endCall(TEST_CALL_ID);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+
+        // ECM timeout.
+        processAllFutureMessages();
+
+        assertFalse(emergencyStateTracker.isInEmergencyMode());
+        assertFalse(emergencyStateTracker.isInEcm());
+        assertFalse(emergencyStateTracker.isInEmergencyCall());
+        verify(phone0).exitEmergencyMode(any(Message.class));
+    }
+
+    private EmergencyStateTracker setupEmergencyStateTracker(
+            boolean isSuplDdsSwitchRequiredForEmergencyCall) {
+        doReturn(mPhoneSwitcher).when(mPhoneSwitcherProxy).getPhoneSwitcher();
+        doNothing().when(mPhoneSwitcher).overrideDefaultDataForEmergency(
+                anyInt(), anyInt(), any());
+        return new EmergencyStateTracker(mContext, mTestableLooper.getLooper(),
+                isSuplDdsSwitchRequiredForEmergencyCall, mPhoneFactoryProxy, mPhoneSwitcherProxy,
+                mTelephonyManagerProxy, mRadioOnHelper, TEST_ECM_EXIT_TIMEOUT_MS);
+    }
+
+    private Phone setupTestPhoneForEmergencyCall(boolean isRoaming, boolean isRadioOn) {
+        Phone testPhone0 = makeTestPhone(0 /* phoneId */, ServiceState.STATE_IN_SERVICE,
+                false /* isEmergencyOnly */);
+        Phone testPhone1 = makeTestPhone(1 /* phoneId */, ServiceState.STATE_OUT_OF_SERVICE,
+                false /* isEmergencyOnly */);
+        List<Phone> phones = new ArrayList<>(2);
+        phones.add(testPhone0);
+        phones.add(testPhone1);
+        doReturn(isRadioOn).when(testPhone0).isRadioOn();
+        doReturn(isRadioOn).when(testPhone1).isRadioOn();
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0);
+        doReturn(2).when(mTelephonyManagerProxy).getPhoneCount();
+        when(mPhoneFactoryProxy.getPhones()).thenReturn(phones.toArray(new Phone[phones.size()]));
+        testPhone0.getServiceState().setRoaming(isRoaming);
+        return testPhone0;
+    }
+
+    private Phone getPhone(int phoneId) {
+        Phone[] phones = mPhoneFactoryProxy.getPhones();
+        return phones[phoneId];
+    }
+
+    private Phone makeTestPhone(int phoneId, int serviceState, boolean isEmergencyOnly) {
+        GsmCdmaPhone phone = mock(GsmCdmaPhone.class);
+        ServiceState testServiceState = new ServiceState();
+        testServiceState.setState(serviceState);
+        testServiceState.setEmergencyOnly(isEmergencyOnly);
+        when(phone.getContext()).thenReturn(mContext);
+        when(phone.getServiceState()).thenReturn(testServiceState);
+        when(phone.getPhoneId()).thenReturn(phoneId);
+        when(phone.getSubId()).thenReturn(0);
+        when(phone.getServiceStateTracker()).thenReturn(mSST);
+        when(phone.getUnitTestMode()).thenReturn(true);
+        // Initialize the phone as a CDMA phone for now for ease of testing ECBM.
+        // Tests can individually override this to GSM if required for the test.
+        doReturn(PhoneConstants.PHONE_TYPE_CDMA).when(phone).getPhoneType();
+        doNothing().when(phone).notifyEmergencyCallRegistrants(anyBoolean());
+        return phone;
+    }
+
+    private void setEcmSupportedConfig(Phone phone, boolean ecmSupported) {
+        CarrierConfigManager cfgManager = (CarrierConfigManager) mContext
+                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        cfgManager.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(
+                CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY,
+                roaminPlmns);
+        cfgManager.getConfigForSubId(phone.getSubId()).putInt(
+                CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
+                suplEmergencyModeType);
+        cfgManager.getConfigForSubId(phone.getSubId())
+                .putString(CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, esExtensionSec);
+    }
+
+    private void setUpAsyncResultForSetEmergencyMode(Phone phone, EmergencyRegResult regResult) {
+        doAnswer((invocation) -> {
+            Object[] args = invocation.getArguments();
+            final Message msg = (Message) args[1];
+            AsyncResult.forMessage(msg, regResult, null);
+            msg.sendToTarget();
+            return null;
+        }).when(phone).setEmergencyMode(anyInt(), any(Message.class));
+    }
+
+    private void setUpAsyncResultForExitEmergencyMode(Phone phone) {
+        doAnswer((invocation) -> {
+            Object[] args = invocation.getArguments();
+            final Message msg = (Message) args[0];
+            AsyncResult.forMessage(msg, null, null);
+            msg.sendToTarget();
+            return null;
+        }).when(phone).exitEmergencyMode(any(Message.class));
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/emergency/RadioOnStateListenerTest.java b/tests/telephonytests/src/com/android/internal/telephony/emergency/RadioOnStateListenerTest.java
new file mode 100644
index 0000000..2c5a873
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/RadioOnStateListenerTest.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2022 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.emergency;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.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.os.AsyncResult;
+import android.os.Handler;
+import android.telephony.ServiceState;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests the RadioOnStateListener, which listens to one Phone and waits until its service state
+ * changes to accepting emergency calls or in service. If it can not find a tower to camp onto for
+ * emergency calls, then it will fail after a timeout period.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RadioOnStateListenerTest extends TelephonyTest {
+
+    private static final long TIMEOUT_MS = 1000;
+
+    @Mock Phone mMockPhone;
+    @Mock RadioOnStateListener.Callback mCallback;
+    @Mock CommandsInterface mMockCi;
+    RadioOnStateListener mListener;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        MockitoAnnotations.initMocks(this);
+        mListener = new RadioOnStateListener();
+        doReturn(mSST).when(mMockPhone).getServiceStateTracker();
+        mMockPhone.mCi = mMockCi;
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mListener.setTimeBetweenRetriesMillis(5000);
+        mListener.setMaxNumRetries(5);
+        mListener.getHandler().removeCallbacksAndMessages(null);
+        // Wait for the queue to clear...
+        waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS /* ms timeout */);
+        mListener = null;
+        super.tearDown();
+    }
+
+    /**
+     * Ensure that we successfully register for the ServiceState changed messages in Telephony.
+     */
+    @Test
+    public void testRegisterForCallback() {
+        mListener.waitForRadioOn(mMockPhone, mCallback, false, false, 0);
+
+        waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
+
+        verify(mMockPhone).unregisterForServiceStateChanged(any(Handler.class));
+        verify(mSatelliteController).unregisterForSatelliteModemStateChanged(anyInt(), any());
+        verify(mMockPhone).registerForServiceStateChanged(any(Handler.class),
+                eq(RadioOnStateListener.MSG_SERVICE_STATE_CHANGED), isNull());
+        verify(mSatelliteController, never()).registerForSatelliteModemStateChanged(
+                anyInt(), any());
+
+        verify(mMockCi).registerForOffOrNotAvailable(any(Handler.class),
+                eq(RadioOnStateListener.MSG_RADIO_OFF_OR_NOT_AVAILABLE), isNull());
+    }
+
+    /**
+     * Ensure that we successfully register for the satellite modem state changed messages.
+     */
+    @Test
+    public void testRegisterForSatelliteCallback() {
+        doReturn(true).when(mSatelliteController).isSatelliteEnabled();
+        mListener.waitForRadioOn(mMockPhone, mCallback, false, false, 0);
+
+        waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
+
+        verify(mSatelliteController).unregisterForSatelliteModemStateChanged(anyInt(), any());
+        verify(mSatelliteController).registerForSatelliteModemStateChanged(anyInt(), any());
+    }
+
+    /**
+     * {@link RadioOnStateListener.Callback#isOkToCall(Phone, int, boolean)} returns true after
+     * service state changes, so we are expecting
+     * {@link RadioOnStateListener.Callback#onComplete(RadioOnStateListener, boolean)} to
+     * return true.
+     */
+    @Test
+    public void testPhoneChangeState_OkToCallTrue() {
+        ServiceState state = new ServiceState();
+        state.setState(ServiceState.STATE_IN_SERVICE);
+        when(mMockPhone.getServiceState()).thenReturn(state);
+        when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE);
+        when(mCallback.isOkToCall(eq(mMockPhone), anyInt(), anyBoolean())).thenReturn(true);
+        mListener.waitForRadioOn(mMockPhone, mCallback, false, false, 0);
+        waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
+
+        mListener.getHandler().obtainMessage(RadioOnStateListener.MSG_SERVICE_STATE_CHANGED,
+                new AsyncResult(null, state, null)).sendToTarget();
+
+        waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
+        verify(mCallback).onComplete(eq(mListener), eq(true));
+    }
+
+    /**
+     * {@link RadioOnStateListener.Callback#isOkToCall(Phone, int, boolean)} returns true after
+     * satellite modem state changes, so we are expecting
+     * {@link RadioOnStateListener.Callback#onComplete(RadioOnStateListener, boolean)} to
+     * return true.
+     */
+    @Test
+    public void testSatelliteChangeState_OkToCallTrue() {
+        ServiceState state = new ServiceState();
+        state.setState(ServiceState.STATE_IN_SERVICE);
+        when(mMockPhone.getServiceState()).thenReturn(state);
+        when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE);
+        when(mCallback.isOkToCall(eq(mMockPhone), anyInt(), anyBoolean())).thenReturn(true);
+        mListener.waitForRadioOn(mMockPhone, mCallback, false, false, 0);
+        waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
+
+        mListener.getHandler().obtainMessage(RadioOnStateListener.MSG_SATELLITE_ENABLED_CHANGED)
+                .sendToTarget();
+
+        waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
+        verify(mCallback).onComplete(eq(mListener), eq(true));
+    }
+
+    /**
+     * We never receive a
+     * {@link RadioOnStateListener.Callback#onComplete(RadioOnStateListener, boolean)} because
+     * {@link RadioOnStateListener.Callback#isOkToCall(Phone, int, boolean)} returns false.
+     */
+    @Test
+    public void testPhoneChangeState_NoOkToCall_Timeout() {
+        ServiceState state = new ServiceState();
+        state.setState(ServiceState.STATE_OUT_OF_SERVICE);
+        when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE);
+        when(mCallback.isOkToCall(eq(mMockPhone), anyInt(), anyBoolean())).thenReturn(false);
+        when(mMockPhone.getServiceState()).thenReturn(state);
+        mListener.waitForRadioOn(mMockPhone, mCallback, false, false, 0);
+        waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
+
+        mListener.getHandler().obtainMessage(RadioOnStateListener.MSG_SERVICE_STATE_CHANGED,
+                new AsyncResult(null, state, null)).sendToTarget();
+
+        waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
+        verify(mCallback, never()).onComplete(any(RadioOnStateListener.class), anyBoolean());
+    }
+
+    /**
+     * Tests {@link RadioOnStateListener.Callback#isOkToCall(Phone, int, boolean)} returning
+     * false and hitting the max number of retries. This should result in
+     * {@link RadioOnStateListener.Callback#onComplete(RadioOnStateListener, boolean)} returning
+     * false.
+     */
+    @Test
+    public void testTimeout_RetryFailure() {
+        ServiceState state = new ServiceState();
+        state.setState(ServiceState.STATE_POWER_OFF);
+        when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE);
+        when(mMockPhone.getServiceState()).thenReturn(state);
+        when(mCallback.isOkToCall(eq(mMockPhone), anyInt(), anyBoolean())).thenReturn(false);
+        mListener.setTimeBetweenRetriesMillis(0/* ms */);
+        mListener.setMaxNumRetries(2);
+
+        // Wait for the timer to expire and check state manually in onRetryTimeout
+        mListener.waitForRadioOn(mMockPhone, mCallback, false, false, 0);
+        waitForDelayedHandlerAction(mListener.getHandler(), TIMEOUT_MS /* delay */, TIMEOUT_MS);
+
+        verify(mCallback).onComplete(eq(mListener), eq(false));
+        verify(mMockPhone, times(2)).setRadioPower(eq(true), eq(false), eq(false), eq(false));
+        verify(mSatelliteController, never()).requestSatelliteEnabled(
+                anyInt(), eq(false), eq(false), any());
+    }
+
+    @Test
+    public void testTimeout_RetryFailure_ForEmergency() {
+        ServiceState state = new ServiceState();
+        state.setState(ServiceState.STATE_POWER_OFF);
+        when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE);
+        when(mMockPhone.getServiceState()).thenReturn(state);
+        when(mCallback.isOkToCall(eq(mMockPhone), anyInt(), anyBoolean())).thenReturn(false);
+        mListener.setTimeBetweenRetriesMillis(0/* ms */);
+        mListener.setMaxNumRetries(2);
+
+        // Wait for the timer to expire and check state manually in onRetryTimeout
+        mListener.waitForRadioOn(mMockPhone, mCallback, true, true, 0);
+        waitForDelayedHandlerAction(mListener.getHandler(), TIMEOUT_MS /* delay */, TIMEOUT_MS);
+
+        verify(mCallback).onComplete(eq(mListener), eq(false));
+        verify(mMockPhone, times(2)).setRadioPower(eq(true), eq(true), eq(true), eq(false));
+        verify(mSatelliteController, never()).requestSatelliteEnabled(
+                anyInt(), eq(false), eq(false), any());
+    }
+
+    @Test
+    public void testTimeout_RetryFailure_WithSatellite() {
+        doReturn(true).when(mSatelliteController).isSatelliteEnabled();
+        ServiceState state = new ServiceState();
+        state.setState(ServiceState.STATE_POWER_OFF);
+        when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE);
+        when(mMockPhone.getServiceState()).thenReturn(state);
+        when(mCallback.isOkToCall(eq(mMockPhone), anyInt(), anyBoolean())).thenReturn(false);
+        mListener.setTimeBetweenRetriesMillis(0/* ms */);
+        mListener.setMaxNumRetries(2);
+
+        // Wait for the timer to expire and check state manually in onRetryTimeout
+        mListener.waitForRadioOn(mMockPhone, mCallback, true, true, 0);
+        waitForDelayedHandlerAction(mListener.getHandler(), TIMEOUT_MS /* delay */, TIMEOUT_MS);
+
+        verify(mCallback).onComplete(eq(mListener), eq(false));
+        verify(mMockPhone, times(2)).setRadioPower(eq(true), eq(true), eq(true), eq(false));
+        verify(mSatelliteController, times(2)).requestSatelliteEnabled(
+                anyInt(), eq(false), eq(false), any());
+    }
+
+    @Test
+    public void testTimeout_OnTimeoutForEmergency() {
+        ServiceState state = new ServiceState();
+        state.setState(ServiceState.STATE_POWER_OFF);
+        when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE);
+        when(mMockPhone.getServiceState()).thenReturn(state);
+        when(mCallback.isOkToCall(eq(mMockPhone), anyInt(), anyBoolean()))
+                .thenReturn(false);
+        when(mCallback.onTimeout(eq(mMockPhone), anyInt(), anyBoolean()))
+                .thenReturn(true);
+        mListener.setTimeBetweenRetriesMillis(0 /* ms */);
+        mListener.setMaxNumRetries(1);
+
+        // Wait for the timer to expire and check state manually in onRetryTimeout
+        mListener.waitForRadioOn(mMockPhone, mCallback, true, true, 100);
+        waitForDelayedHandlerAction(mListener.getHandler(), TIMEOUT_MS /* delay */, TIMEOUT_MS);
+
+        verify(mCallback).onTimeout(eq(mMockPhone), anyInt(), anyBoolean());
+        verify(mCallback).onComplete(eq(mListener), eq(true));
+    }
+}
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 93c9142..d4850c8 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccConnectorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccConnectorTest.java
@@ -17,6 +17,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -354,6 +355,29 @@
         assertEquals(mConnector.mAvailableState, mConnector.getCurrentState());
     }
 
+    @Test
+    public void testConnectedState_serviceDisconnected() throws Exception {
+        // Kick off the asynchronous command.
+        prepareEuiccApp(true /* hasPermission */, true /* requiresBindPermission */,
+                true /* hasPriority */);
+        mConnector = new EuiccConnector(mContext, mLooper.getLooper());
+        mConnector.getEid(CARD_ID, new EuiccConnector.GetEidCommandCallback() {
+            @Override public void onGetEidComplete(String eid) {}
+            @Override public void onEuiccServiceUnavailable() {}
+        });
+        mLooper.dispatchAll();
+        assertEquals(mConnector.mConnectedState, mConnector.getCurrentState());
+        // Now, pretend the remote process died.
+        mConnector.onServiceDisconnected(null /* name */);
+        mLooper.dispatchAll();
+        assertEquals(mConnector.mDisconnectedState, mConnector.getCurrentState());
+        // After binder timeout, should now drop back to available state.
+        mLooper.moveTimeForward(EuiccConnector.BIND_TIMEOUT_MILLIS);
+        mLooper.dispatchAll();
+        assertEquals(mConnector.mAvailableState, mConnector.getCurrentState());
+        assertNull(mConnector.getBinder());
+    }
+
     private void prepareEuiccApp(
             boolean hasPermission, boolean requiresBindPermission, boolean hasPriority) {
         when(mPackageManager.checkPermission(
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 85db492..fc8dfbf 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
@@ -65,6 +65,8 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.euicc.EuiccConnector.GetOtaStatusCommandCallback;
 import com.android.internal.telephony.euicc.EuiccConnector.OtaStatusChangedCallback;
@@ -75,7 +77,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestRule;
@@ -90,6 +91,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
 
 @RunWith(AndroidJUnit4.class)
 public class EuiccControllerTest extends TelephonyTest {
@@ -129,7 +131,7 @@
     private static final int SUBSCRIPTION_ID = 12345;
     private static final String ICC_ID = "54321";
     private static final int CARD_ID = 25;
-    private static final int PORT_INDEX = 0;
+    private static final int REMOVABLE_CARD_ID = 26;
 
     // Mocked classes
     private EuiccConnector mMockConnector;
@@ -320,8 +322,8 @@
                 SUBSCRIPTION, false /* complete */, null /* result */);
         verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR,
                 0 /* detailedCode */);
-        verify(mMockConnector).getDownloadableSubscriptionMetadata(anyInt(), any(), anyBoolean(),
-                any());
+        verify(mMockConnector).getDownloadableSubscriptionMetadata(anyInt(), anyInt(), any(),
+                anyBoolean(), anyBoolean(), any());
     }
 
     @Test
@@ -612,7 +614,6 @@
         assertFalse(mController.mCalledRefreshSubscriptionsAndSendResult);
     }
 
-    @Ignore("b/255697307")
     @Test
     @DisableCompatChanges({EuiccManager.SHOULD_RESOLVE_PORT_INDEX_FOR_APPS})
     public void testDownloadSubscription_noPrivileges_hasCarrierPrivileges_multiSim()
@@ -789,6 +790,70 @@
     }
 
     @Test
+    public void testGetResolvedPortIndexForSubscriptionSwitchWithOutMEP() throws Exception {
+        setUpUiccSlotData();
+        assertEquals(TelephonyManager.DEFAULT_PORT_INDEX,
+                mController.getResolvedPortIndexForSubscriptionSwitch(CARD_ID));
+    }
+
+    @Test
+    public void testGetResolvedPortIndexForSubscriptionSwitchWithMEP() throws Exception {
+        setUpUiccSlotDataWithMEP();
+        when(mUiccSlot.getPortList()).thenReturn(new int[]{0, 1});
+        when(mUiccSlot.isPortActive(TelephonyManager.DEFAULT_PORT_INDEX)).thenReturn(false);
+        when(mUiccSlot.isPortActive(1)).thenReturn(true);
+        when(mTelephonyManager.getActiveModemCount()).thenReturn(2);
+        assertEquals(1, mController.getResolvedPortIndexForSubscriptionSwitch(CARD_ID));
+    }
+
+    @Test
+    public void testGetResolvedPortIndexForSubscriptionSwitchWithUiccSlotNull() throws Exception {
+        assertEquals(TelephonyManager.DEFAULT_PORT_INDEX,
+                mController.getResolvedPortIndexForSubscriptionSwitch(CARD_ID));
+    }
+
+    @Test
+    public void testGetResolvedPortIndexForSubscriptionSwitchWithPsimActiveAndSS()
+            throws Exception {
+        when(mUiccController.getUiccSlot(anyInt())).thenReturn(mUiccSlot);
+        when(mUiccSlot.isRemovable()).thenReturn(true);
+        when(mUiccSlot.isEuicc()).thenReturn(false);
+        when(mUiccSlot.isActive()).thenReturn(true);
+        when(mTelephonyManager.getActiveModemCount()).thenReturn(1);
+        assertEquals(TelephonyManager.DEFAULT_PORT_INDEX,
+                mController.getResolvedPortIndexForSubscriptionSwitch(CARD_ID));
+    }
+
+    @Test
+    public void testGetResolvedPortIndexForSubscriptionSwitchWithPsimInActiveAndSS()
+            throws Exception {
+        setUpUiccSlotDataWithMEP();
+        when(mUiccSlot.getPortList()).thenReturn(new int[]{0});
+        when(mUiccSlot.isPortActive(TelephonyManager.DEFAULT_PORT_INDEX)).thenReturn(true);
+        when(mTelephonyManager.getActiveModemCount()).thenReturn(1);
+        assertEquals(TelephonyManager.DEFAULT_PORT_INDEX,
+                mController.getResolvedPortIndexForSubscriptionSwitch(CARD_ID));
+    }
+
+    @Test
+    public void testgetResolvedPortIndexForDisableSubscriptionForNoActiveSubscription()
+            throws Exception {
+        when(mSubscriptionManager.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(null);
+        assertEquals(-1,
+                mController.getResolvedPortIndexForDisableSubscription(
+                        CARD_ID, PACKAGE_NAME, true));
+    }
+
+    @Test
+    public void testgetResolvedPortIndexForDisableSubscriptionForActiveSubscriptions()
+            throws Exception {
+        setActiveSubscriptionInfoInMEPMode();
+        assertEquals(1,
+                mController.getResolvedPortIndexForDisableSubscription(
+                        CARD_ID, PACKAGE_NAME, false));
+    }
+
+    @Test
     public void testDeleteSubscription_noPrivileges() throws Exception {
         setHasWriteEmbeddedPermission(false);
         prepareOperationSubscription(false /* hasPrivileges */);
@@ -889,7 +954,6 @@
                 anyBoolean(), any(), anyBoolean());
     }
 
-    @Ignore("b/255697307")
     @Test
     @DisableCompatChanges({EuiccManager.SHOULD_RESOLVE_PORT_INDEX_FOR_APPS})
     public void testSwitchToSubscription_emptySubscription_success() throws Exception {
@@ -1230,12 +1294,171 @@
                 SWITCH_WITHOUT_PORT_INDEX_EXCEPTION_ON_DISABLE));
     }
 
+    @Test
+    @EnableCompatChanges({EuiccManager.INACTIVE_PORT_AVAILABILITY_CHECK})
+    public void testIsSimPortAvailable_invalidCase() {
+        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));
+    }
+
+    @Test
+    @EnableCompatChanges({EuiccManager.INACTIVE_PORT_AVAILABILITY_CHECK})
+    public void testIsSimPortAvailable_port_active() throws Exception {
+        setUiccCardInfos(false, true, true);
+
+        // port has empty iccid
+        assertTrue(mController.isSimPortAvailable(CARD_ID, 0, TEST_PACKAGE_NAME));
+
+        // Set port is active, has valid iccid(may be boot profile) and UiccProfile is empty
+        setUiccCardInfos(false, true, false);
+        when(mUiccController.getUiccPortForSlot(anyInt(), anyInt())).thenReturn(mUiccPort);
+        when(mUiccPort.getUiccProfile()).thenReturn(mUiccProfile);
+        when(mUiccProfile.isEmptyProfile()).thenReturn(true);
+        assertTrue(mController.isSimPortAvailable(CARD_ID, 0, TEST_PACKAGE_NAME));
+
+        // port is active, valid iccid, not empty profile but Phone object is null
+        when(mUiccPort.getUiccProfile()).thenReturn(mUiccProfile);
+        when(mUiccProfile.isEmptyProfile()).thenReturn(false);
+        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone});
+        // logicalSlotIndex of port#0 is 1, Phone object should be null
+        assertFalse(mController.isSimPortAvailable(CARD_ID, 0, TEST_PACKAGE_NAME));
+
+        // port is active, valid iccid, not empty profile but no carrier privileges
+        when(mUiccPort.getUiccProfile()).thenReturn(mUiccProfile);
+        when(mUiccProfile.isEmptyProfile()).thenReturn(false);
+        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone, mPhone});
+        when(mPhone.getCarrierPrivilegesTracker()).thenReturn(null);
+        assertFalse(mController.isSimPortAvailable(CARD_ID, 0, TEST_PACKAGE_NAME));
+        when(mPhone.getCarrierPrivilegesTracker()).thenReturn(mCarrierPrivilegesTracker);
+        when(mCarrierPrivilegesTracker.getCarrierPrivilegeStatusForPackage(TEST_PACKAGE_NAME))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+        assertFalse(mController.isSimPortAvailable(CARD_ID, 0, TEST_PACKAGE_NAME));
+
+        // port is active, valid iccid, not empty profile and has carrier privileges
+        when(mCarrierPrivilegesTracker.getCarrierPrivilegeStatusForPackage(TEST_PACKAGE_NAME))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+        assertTrue(mController.isSimPortAvailable(CARD_ID, 0, TEST_PACKAGE_NAME));
+    }
+
+    @Test
+    @EnableCompatChanges({EuiccManager.INACTIVE_PORT_AVAILABILITY_CHECK})
+    public void testIsSimPortAvailable_port_inActive() {
+        setUiccCardInfos(false, false, true);
+        when(mUiccController.getUiccSlots()).thenReturn(new UiccSlot[]{mUiccSlot});
+        when(mUiccSlot.isRemovable()).thenReturn(true);
+
+        // Check getRemovableNonEuiccSlot null case
+        when(mUiccSlot.isEuicc()).thenReturn(true);
+        assertFalse(mController.isSimPortAvailable(CARD_ID, 0, TEST_PACKAGE_NAME));
+
+        // Check getRemovableNonEuiccSlot isActive() false case
+        when(mUiccSlot.isEuicc()).thenReturn(false);
+        when(mUiccSlot.isActive()).thenReturn(false);
+        assertFalse(mController.isSimPortAvailable(CARD_ID, 0, TEST_PACKAGE_NAME));
+
+        // assert false,multisim is not enabled
+        when(mUiccSlot.isEuicc()).thenReturn(false);
+        when(mUiccSlot.isActive()).thenReturn(true);
+        when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(TEST_PACKAGE_NAME))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+        when(mTelephonyManager.isMultiSimEnabled()).thenReturn(false);
+        assertFalse(mController.isSimPortAvailable(CARD_ID, 0, TEST_PACKAGE_NAME));
+
+        // assert false, caller does not have carrier privileges
+        setHasWriteEmbeddedPermission(false);
+        when(mTelephonyManager.isMultiSimEnabled()).thenReturn(true);
+        when(mUiccSlot.getPortList()).thenReturn(new int[] {0});
+        when(mSubscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(anyInt())).thenReturn(
+                new SubscriptionInfo.Builder().build());
+        when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(TEST_PACKAGE_NAME))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+        assertFalse(mController.isSimPortAvailable(CARD_ID, 0, TEST_PACKAGE_NAME));
+
+        // assert true, caller does not have carrier privileges but has write_embedded permission
+        setHasWriteEmbeddedPermission(true);
+        assertTrue(mController.isSimPortAvailable(CARD_ID, 0, TEST_PACKAGE_NAME));
+
+        // assert true, caller has carrier privileges
+        setHasWriteEmbeddedPermission(false);
+        when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(TEST_PACKAGE_NAME))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+        assertTrue(mController.isSimPortAvailable(CARD_ID, 0, TEST_PACKAGE_NAME));
+    }
+
+    @Test
+    @DisableCompatChanges({EuiccManager.INACTIVE_PORT_AVAILABILITY_CHECK})
+    public void testIsSimPortAvailable_port_inActive_disable_compactChange() {
+        setUiccCardInfos(false, false, true);
+        when(mUiccController.getUiccSlots()).thenReturn(new UiccSlot[]{mUiccSlot});
+        when(mUiccSlot.isRemovable()).thenReturn(true);
+        when(mUiccSlot.isEuicc()).thenReturn(false);
+        when(mUiccSlot.isActive()).thenReturn(true);
+
+        when(mTelephonyManager.isMultiSimEnabled()).thenReturn(true);
+        when(mUiccSlot.getPortList()).thenReturn(new int[] {0});
+        when(mSubscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(anyInt())).thenReturn(
+                new SubscriptionInfo.Builder().build());
+        when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(TEST_PACKAGE_NAME))
+                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+        setHasWriteEmbeddedPermission(true);
+
+        // Even though all conditions are true, isSimPortAvailable should return false as
+        // compact change is disabled
+        assertFalse(mController.isSimPortAvailable(CARD_ID, 0, TEST_PACKAGE_NAME));
+    }
+
+
+    private void setUiccCardInfos(boolean isMepSupported, boolean isPortActive,
+            boolean isEmptyPort) {
+        List<UiccPortInfo> euiccPortInfoList;
+        if (isMepSupported) {
+            euiccPortInfoList = Arrays.asList(
+                    new UiccPortInfo(isEmptyPort ? "" : ICC_ID /* iccId */, 0 /* portIdx */,
+                            isPortActive ? 1 : -1 /* logicalSlotIdx */,
+                            isPortActive /* isActive */),
+                    new UiccPortInfo(isEmptyPort ? "" : ICC_ID /* iccId */, 1 /* portIdx */,
+                            -1 /* logicalSlotIdx */,
+                            isPortActive /* isActive */));
+        } else {
+            euiccPortInfoList = Collections.singletonList(
+                    new UiccPortInfo(isEmptyPort ? "" : ICC_ID /* iccId */, 0 /* portIdx */,
+                            isPortActive ? 1 : -1 /* logicalSlotIdx */,
+                            isPortActive /* isActive */)
+                    );
+        }
+
+        UiccCardInfo cardInfo1 = new UiccCardInfo(true, CARD_ID, "", 0,
+                false /* isRemovable */,
+                isMepSupported /* isMultipleEnabledProfileSupported */,
+                euiccPortInfoList);
+        UiccCardInfo cardInfo2 = new UiccCardInfo(false /* isEuicc */,
+                REMOVABLE_CARD_ID /* cardId */,
+                "", 0, true /* isRemovable */,
+                false /* isMultipleEnabledProfileSupported */,
+                Collections.singletonList(
+                        new UiccPortInfo("" /* iccId */, 0 /* portIdx */,
+                                0 /* logicalSlotIdx */, true /* isActive */)));
+        ArrayList<UiccCardInfo> cardInfos = new ArrayList<>();
+        cardInfos.add(cardInfo1);
+        cardInfos.add(cardInfo2);
+        when(mTelephonyManager.getUiccCardsInfo()).thenReturn(cardInfos);
+    }
+
     private void setUpUiccSlotData() {
         when(mUiccController.getUiccSlot(anyInt())).thenReturn(mUiccSlot);
         // TODO(b/199559633): Add test cases for isMultipleEnabledProfileSupported true case
         when(mUiccSlot.isMultipleEnabledProfileSupported()).thenReturn(false);
     }
 
+    private void setUpUiccSlotDataWithMEP() {
+        when(mUiccController.getUiccSlot(anyInt())).thenReturn(mUiccSlot);
+        when(mUiccSlot.isMultipleEnabledProfileSupported()).thenReturn(true);
+    }
+
     private void setGetEidPermissions(
             boolean hasPhoneStatePrivileged, boolean hasCarrierPrivileges) throws Exception {
         doReturn(hasPhoneStatePrivileged
@@ -1263,6 +1486,7 @@
             throws Exception {
         SubscriptionInfo.Builder builder = new SubscriptionInfo.Builder()
                 .setSimSlotIndex(0)
+                .setPortIndex(mTelephonyManager.DEFAULT_PORT_INDEX)
                 .setDisplayNameSource(SubscriptionManager.NAME_SOURCE_CARRIER_ID)
                 .setEmbedded(true);
         if (hasPrivileges) {
@@ -1300,11 +1524,13 @@
                 .setNativeAccessRules(hasPrivileges ? new UiccAccessRule[] { ACCESS_RULE } : null)
                 .setEmbedded(true)
                 .setCardId(CARD_ID)
+                .setPortIndex(mTelephonyManager.DEFAULT_PORT_INDEX)
                 .build();
         SubscriptionInfo subInfo2 = new SubscriptionInfo.Builder()
                 .setNativeAccessRules(hasPrivileges ? new UiccAccessRule[] { ACCESS_RULE } : null)
                 .setEmbedded(true)
                 .setCardId(2)
+                .setPortIndex(TelephonyManager.DEFAULT_PORT_INDEX)
                 .build();
         when(mSubscriptionManager.canManageSubscription(subInfo1, PACKAGE_NAME)).thenReturn(
                 hasPrivileges);
@@ -1314,6 +1540,26 @@
         when(mSubscriptionManager.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(subInfos);
     }
 
+    private void setActiveSubscriptionInfoInMEPMode()
+            throws Exception {
+        SubscriptionInfo subInfo1 = new SubscriptionInfo.Builder()
+                .setEmbedded(true)
+                .setCardId(CARD_ID)
+                .setPortIndex(TelephonyManager.DEFAULT_PORT_INDEX)
+                .build();
+        SubscriptionInfo subInfo2 = new SubscriptionInfo.Builder()
+                .setEmbedded(true)
+                .setCardId(CARD_ID)
+                .setPortIndex(1)
+                .build();
+        when(mSubscriptionManager.canManageSubscription(subInfo1, PACKAGE_NAME)).thenReturn(
+                false);
+        when(mSubscriptionManager.canManageSubscription(subInfo2, PACKAGE_NAME)).thenReturn(
+                true);
+        ArrayList<SubscriptionInfo> subInfos = new ArrayList<>(Arrays.asList(subInfo1, subInfo2));
+        when(mSubscriptionManager.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(subInfos);
+    }
+
     private void prepareOperationSubscription(boolean hasPrivileges) throws Exception {
         SubscriptionInfo subInfo = new SubscriptionInfo.Builder()
                 .setId(SUBSCRIPTION_ID)
@@ -1404,7 +1650,7 @@
             @Override
             public Void answer(InvocationOnMock invocation) throws Exception {
                 EuiccConnector.GetMetadataCommandCallback cb = invocation
-                        .getArgument(3 /* resultCallback */);
+                        .getArgument(5 /* resultCallback */);
                 if (complete) {
                     cb.onGetMetadataComplete(CARD_ID, result);
                 } else {
@@ -1412,8 +1658,8 @@
                 }
                 return null;
             }
-        }).when(mMockConnector).getDownloadableSubscriptionMetadata(anyInt(), any(), anyBoolean(),
-                any());
+        }).when(mMockConnector).getDownloadableSubscriptionMetadata(anyInt(), anyInt(), any(),
+                anyBoolean(), anyBoolean(), any());
     }
 
     private void callGetDownloadableSubscriptionMetadata(DownloadableSubscription subscription,
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java
index a847a24..c261afe 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java
@@ -36,6 +36,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.BroadcastOptions;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.content.BroadcastReceiver;
@@ -55,9 +56,9 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.MediumTest;
 
+import com.android.ims.ImsManager;
 import com.android.internal.telephony.FakeSmsContentProvider;
 import com.android.internal.telephony.InboundSmsHandler;
 import com.android.internal.telephony.InboundSmsTracker;
@@ -192,9 +193,9 @@
                 InboundSmsHandler.SOURCE_NOT_INJECTED);
         doReturn(mInboundSmsTracker).when(mTelephonyComponentFactory)
                 .makeInboundSmsTracker(any(Context.class), nullable(byte[].class), anyLong(),
-                anyInt(), anyBoolean(),
-                anyBoolean(), nullable(String.class), nullable(String.class),
-                nullable(String.class), anyBoolean(), anyInt(), anyInt());
+                        anyInt(), anyBoolean(),
+                        anyBoolean(), nullable(String.class), nullable(String.class),
+                        nullable(String.class), anyBoolean(), anyInt(), anyInt());
 
         createInboundSmsTrackerMultiSim();
 
@@ -203,12 +204,11 @@
                 Telephony.Sms.CONTENT_URI.getAuthority(), mContentProvider);
 
         mGsmInboundSmsHandler = GsmInboundSmsHandler.makeInboundSmsHandler(mContext,
-                mSmsStorageMonitor, mPhone);
+                mSmsStorageMonitor, mPhone, mTestableLooper.getLooper());
         mSmsFilters = new ArrayList<>();
         mSmsFilters.add(mSmsFilter);
         mSmsFilters.add(mSmsFilter2);
         mGsmInboundSmsHandler.setSmsFiltersForTesting(mSmsFilters);
-        monitorTestableLooper(new TestableLooper(mGsmInboundSmsHandler.getHandler().getLooper()));
 
         doReturn(mGsmInboundSmsHandler).when(mPhone).getInboundSmsHandler(false);
         doReturn(mCdmaInboundSmsHandler).when(mPhone).getInboundSmsHandler(true);
@@ -278,8 +278,8 @@
         assertEquals("WaitingState", getCurrentState().getName());
         if (allowBgActivityStarts) {
             Bundle broadcastOptions = mContextFixture.getLastBroadcastOptions();
-            assertTrue(broadcastOptions
-                    .getBoolean("android:broadcast.allowBackgroundActivityStarts"));
+            BroadcastOptions brOptions = new BroadcastOptions(broadcastOptions);
+            assertTrue(brOptions.allowsBackgroundActivityStarts());
         }
 
         mContextFixture.sendBroadcastToOrderedBroadcastReceivers();
@@ -310,7 +310,6 @@
         processAllMessages();
     }
 
-    @FlakyTest
     @Test
     @MediumTest
     public void testNewSms() {
@@ -330,7 +329,6 @@
         verifySmsFiltersInvoked(times(1));
     }
 
-    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testNewSmsFromBlockedNumber_noBroadcastsSent() {
@@ -348,7 +346,6 @@
         verifySmsFiltersInvoked(times(1));
     }
 
-    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testNewSmsWithUserLocked_notificationShown() {
@@ -375,7 +372,6 @@
                 any(Notification.class));
     }
 
-    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testNewSmsFromBlockedNumberWithUserLocked_noNotificationShown() {
@@ -405,7 +401,6 @@
                 any(Notification.class));
     }
 
-    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testNewSms_filterInvoked_noBroadcastsSent() {
@@ -431,7 +426,6 @@
                 anyBoolean(), anyBoolean(), Mockito.<List<InboundSmsHandler.SmsFilter>>any());
     }
 
-    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testNewSms_filterChaining_noBroadcastsSent() {
@@ -483,7 +477,6 @@
         assertEquals("IdleState", getCurrentState().getName());
     }
 
-    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testClass0Sms() {
@@ -515,7 +508,6 @@
         verifySmsFiltersInvoked(times(1));
     }
 
-    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testBroadcastSms() {
@@ -556,7 +548,6 @@
         verifySmsFiltersInvoked(times(2));
     }
 
-    @FlakyTest
     @Test
     @MediumTest
     public void testInjectSms() {
@@ -617,7 +608,6 @@
                 InboundSmsHandler.SOURCE_NOT_INJECTED);
     }
 
-    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testMultiPartSmsWithIncompleteWAP() {
@@ -678,12 +668,13 @@
         assertEquals("IdleState", getCurrentState().getName());
         // verify there are three segments in the db and only one of them is not marked as deleted.
         assertEquals(3, mContentProvider.getNumRows());
-        assertEquals(1, mContentProvider.query(sRawUri, null, "deleted=0", null, null).getCount());
 
+        Cursor c = mContentProvider.query(sRawUri, null, "deleted=0", null, null);
+        assertEquals(1, c.getCount());
         verifySmsFiltersInvoked(times(1));
+        c.close();
     }
 
-    @FlakyTest
     @Test
     @MediumTest
     public void testMultiPartSms() {
@@ -757,7 +748,6 @@
         assertEquals("IdleState", getCurrentState().getName());
     }
 
-    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testMultiPartIncompleteSms() {
@@ -820,9 +810,9 @@
         // State machine should go back to idle
         assertEquals("IdleState", getCurrentState().getName());
         verifySmsFiltersInvoked(never());
+        c.close();
     }
 
-    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testMultiPartSmsWithInvalidSeqNumber() {
@@ -882,7 +872,6 @@
         verifySmsFiltersInvoked(never());
     }
 
-    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testMultipartSmsFromBlockedNumber_noBroadcastsSent() {
@@ -921,7 +910,6 @@
         verifySmsFiltersInvoked(times(1));
     }
 
-    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testMultipartSmsFromBlockedEmail_noBroadcastsSent() {
@@ -977,7 +965,6 @@
         verifySmsFiltersInvoked(times(1));
     }
 
-    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testMultipartSms_filterInvoked_noBroadcastsSent() {
@@ -1027,7 +1014,6 @@
                 anyBoolean(), anyBoolean(), Mockito.<List<InboundSmsHandler.SmsFilter>>any());
     }
 
-    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testBroadcastUndeliveredUserLocked() throws Exception {
@@ -1085,7 +1071,6 @@
         verifySmsFiltersInvoked(times(1));
     }
 
-    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testBroadcastUndeliveredUserUnlocked() throws Exception {
@@ -1123,7 +1108,6 @@
         verifySmsFiltersInvoked(times(1));
     }
 
-    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testBroadcastUndeliveredDeleted() throws Exception {
@@ -1164,7 +1148,6 @@
         verifySmsFiltersInvoked(never());
     }
 
-    @FlakyTest
     @Test
     @MediumTest
     public void testBroadcastUndeliveredMultiPart() throws Exception {
@@ -1180,7 +1163,7 @@
         //return InboundSmsTracker objects corresponding to the 2 parts
         doReturn(mInboundSmsTrackerPart1).doReturn(mInboundSmsTrackerPart2).
                 when(mTelephonyComponentFactory).makeInboundSmsTracker(any(Context.class),
-                any(Cursor.class), anyBoolean());
+                        any(Cursor.class), anyBoolean());
 
         SmsBroadcastUndelivered.initialize(mContext, mGsmInboundSmsHandler, mCdmaInboundSmsHandler);
         // wait for ScanRawTableThread
@@ -1191,7 +1174,6 @@
         verifySmsFiltersInvoked(times(1));
     }
 
-    @FlakyTest // temporarily disabled, see b/182498318
     @Test
     @MediumTest
     public void testBroadcastUndeliveredMultiSim() throws Exception {
@@ -1251,4 +1233,12 @@
         assertEquals("IdleState", getCurrentState().getName());
 
     }
+
+    @Test
+    public void testSetImsManager() {
+        ImsManager imsManager = Mockito.mock(ImsManager.class);
+        transitionFromStartupToIdle();
+        assertTrue(mGsmInboundSmsHandler.setImsManager(imsManager));
+    }
 }
+
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmMmiCodeTest.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmMmiCodeTest.java
index c0444f1..1c1ca0f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmMmiCodeTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmMmiCodeTest.java
@@ -333,4 +333,3 @@
         doReturn(bundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
     }
 }
-
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java
index 7da20d8..8374daa 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java
@@ -66,7 +66,6 @@
 import com.android.internal.telephony.ISub;
 import com.android.internal.telephony.SMSDispatcher;
 import com.android.internal.telephony.SmsDispatchersController;
-import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.TelephonyTestUtils;
 import com.android.internal.telephony.TestApplication;
@@ -231,7 +230,8 @@
         restoreInstance(Singleton.class, "mInstance", mIActivityManagerSingleton);
         restoreInstance(ActivityManager.class, "IActivityManagerSingleton", null);
         Context realContext = TestApplication.getAppContext();
-        realContext.registerReceiver(mTestReceiver, new IntentFilter(TEST_INTENT));
+        realContext.registerReceiver(mTestReceiver, new IntentFilter(TEST_INTENT),
+                Context.RECEIVER_EXPORTED);
     }
 
     @Test
@@ -376,6 +376,7 @@
 
     @Test
     @SmallTest
+    @Ignore("b/256282780")
     public void testSendSmsByCarrierApp() throws Exception {
         mockCarrierApp();
         mockCarrierAppStubResults(CarrierMessagingService.SEND_STATUS_OK,
@@ -383,7 +384,9 @@
         registerTestIntentReceiver();
 
         PendingIntent pendingIntent = PendingIntent.getBroadcast(TestApplication.getAppContext(), 0,
-                new Intent(TEST_INTENT), PendingIntent.FLAG_MUTABLE);
+                new Intent(TEST_INTENT)
+                        .setPackage(TestApplication.getAppContext().getPackageName()),
+                PendingIntent.FLAG_MUTABLE);
         mReceivedTestIntent = false;
 
         mGsmSmsDispatcher.sendText("6501002000", "121" /*scAddr*/, "test sms",
@@ -438,9 +441,13 @@
 
         ArrayList<PendingIntent> sentIntents = new ArrayList<>();
         PendingIntent sentIntent1 = PendingIntent.getBroadcast(TestApplication.getAppContext(), 0,
-                new Intent(TEST_INTENT), PendingIntent.FLAG_MUTABLE);
+                new Intent(TEST_INTENT)
+                        .setPackage(TestApplication.getAppContext().getPackageName()),
+                PendingIntent.FLAG_MUTABLE);
         PendingIntent sentIntent2 = PendingIntent.getBroadcast(TestApplication.getAppContext(), 0,
-                new Intent(TEST_INTENT), PendingIntent.FLAG_MUTABLE);
+                new Intent(TEST_INTENT)
+                        .setPackage(TestApplication.getAppContext().getPackageName()),
+                PendingIntent.FLAG_MUTABLE);
         sentIntents.add(sentIntent1);
         sentIntents.add(sentIntent2);
 
@@ -450,6 +457,7 @@
 
     @Test
     @SmallTest
+    @Ignore("b/256282780")
     public void testSendMultipartSmsByCarrierApp() throws Exception {
         mockCarrierApp();
         mockCarrierAppStubResults(CarrierMessagingService.SEND_STATUS_OK,
@@ -510,7 +518,11 @@
 
     @Test
     public void testSendTextWithMessageRef() throws Exception {
-        int messageRef = mGsmSmsDispatcher.nextMessageRef() + 1;
+        int messageRef = mGsmSmsDispatcher.nextMessageRef();
+        if (mGsmSmsDispatcher.isMessageRefIncrementViaTelephony()) {
+            messageRef += 1;
+        }
+
         mGsmSmsDispatcher.sendText("111", "222" /*scAddr*/, TAG,
                 null, null, null, null, false, -1, false, -1, false, 0L);
 
@@ -518,7 +530,7 @@
         verify(mSimulatedCommandsVerifier).sendSMS(anyString(), pduCaptor.capture(),
                 any(Message.class));
         byte[] pdu = IccUtils.hexStringToBytes(pduCaptor.getValue());
-        assertEquals(pdu[1], messageRef);
+        assertEquals(messageRef, pdu[1]);
     }
 
     @Test
@@ -527,7 +539,11 @@
         parts.add("segment1");
         parts.add("segment2");
         parts.add("segment3");
-        int messageRef = mGsmSmsDispatcher.nextMessageRef() + parts.size();
+
+        int messageRef = mGsmSmsDispatcher.nextMessageRef();
+        if (mGsmSmsDispatcher.isMessageRefIncrementViaTelephony()) {
+            messageRef += parts.size();
+        }
         mGsmSmsDispatcher.sendMultipartText("6501002000" /*destAddr*/, "222" /*scAddr*/, parts,
                 null, null, null, null, false, -1, false, -1, 0L);
         waitForMs(150);
@@ -539,7 +555,7 @@
         verify(mSimulatedCommandsVerifier).sendSMS(anyString(), pduCaptor.capture(),
                 any(Message.class));
         byte[] pdu = IccUtils.hexStringToBytes(pduCaptor.getValue());
-        assertEquals(pdu[1], messageRef);
+        assertEquals(messageRef, pdu[1]);
     }
 
     @Test
@@ -549,20 +565,16 @@
         doReturn(mIsimUiccRecords).when(mPhone).getIccRecords();
         Message msg = mGsmSmsDispatcher.obtainMessage(17);
         mPhone.getIccRecords().setSmssTpmrValue(-1, msg);
-        if (isSubscriptionManagerServiceEnabled()) {
-            mSubscriptionManagerService.setLastUsedTPMessageReference(mPhone.getSubId(), -1);
-        } else {
-            SubscriptionController.getInstance().updateMessageRef(mPhone.getSubId(), -1);
-        }
+        mSubscriptionManagerService.setLastUsedTPMessageReference(mPhone.getSubId(), -1);
 
         mGsmSmsDispatcher.sendText("111", "222" /*scAddr*/, TAG,
                 null, null, null, null, false, -1, false, -1, false, 0L);
 
-        ArgumentCaptor<String> pduCaptor1 = ArgumentCaptor.forClass(String.class);
-        verify(mSimulatedCommandsVerifier).sendSMS(anyString(), pduCaptor1.capture(),
+        ArgumentCaptor<String> pduCaptor = ArgumentCaptor.forClass(String.class);
+        verify(mSimulatedCommandsVerifier).sendSMS(anyString(), pduCaptor.capture(),
                 any(Message.class));
-        byte[] pdu1 = IccUtils.hexStringToBytes(pduCaptor1.getValue());
-        assertEquals(pdu1[1], 0);
+        byte[] pdu = IccUtils.hexStringToBytes(pduCaptor.getValue());
+        assertEquals(0, pdu[1]);
     }
 
     @Test
@@ -571,15 +583,14 @@
                 mSimulatedCommands);
         doReturn(mIsimUiccRecords).when(mPhone).getIccRecords();
         Message msg = mGsmSmsDispatcher.obtainMessage(17);
-
-        mPhone.getIccRecords().setSmssTpmrValue(255, null);
+        mPhone.getIccRecords().setSmssTpmrValue(255, msg);
         mGsmSmsDispatcher.sendText("111", "222" /*scAddr*/, TAG,
                 null, null, null, null, false, -1, false, -1, false, 0L);
 
-        ArgumentCaptor<String> pduCaptor2 = ArgumentCaptor.forClass(String.class);
-        verify(mSimulatedCommandsVerifier).sendSMS(anyString(), pduCaptor2.capture(),
+        ArgumentCaptor<String> pduCaptor = ArgumentCaptor.forClass(String.class);
+        verify(mSimulatedCommandsVerifier).sendSMS(anyString(), pduCaptor.capture(),
                 any(Message.class));
-        byte[] pdu2 = IccUtils.hexStringToBytes(pduCaptor2.getValue());
-        assertEquals(pdu2[1], 0);
+        byte[] pdu = IccUtils.hexStringToBytes(pduCaptor.getValue());
+        assertEquals(0, pdu[1]);
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadHandlerTest.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadHandlerTest.java
new file mode 100644
index 0000000..c6041c8
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/UsimDataDownloadHandlerTest.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2022 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.gsm;
+
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.content.res.Resources;
+import android.os.AsyncResult;
+import android.os.Message;
+import android.telephony.ims.stub.ImsSmsImplBase;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.ims.ImsManager;
+import com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.GsmCdmaPhone;
+import com.android.internal.telephony.InboundSmsHandler;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.uicc.IccIoResult;
+import com.android.internal.telephony.uicc.IccUtils;
+import com.android.internal.util.HexDump;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class UsimDataDownloadHandlerTest extends TelephonyTest {
+    // Mocked classes
+    private CommandsInterface mMockCi;
+    protected GsmCdmaPhone mMockPhone;
+    private ImsManager mMockImsManager;
+    private Resources mMockResources;
+
+    private UsimDataDownloadHandler mUsimDataDownloadHandler;
+    private Phone mPhoneObj;
+    private Phone[] mPhoneslist;
+    private int mPhoneId;
+    private int mSlotId = 0;
+    private int mToken = 0;
+    private int mSmsSource = 1;
+    private byte[] mTpdu;
+    private byte[] mSmsAckPdu;
+    //Envelope is created as per TS.131.111 for SMS-PP data downwnload operation
+    private String mEnvelope = "D11502028281060591896745F306068199201269231300";
+    //SMS TPDU for Class2 according to TS.123.040
+    private String mPdu = "07914151551512f221110A81785634121000000666B2996C2603";
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        mMockCi = Mockito.mock(CommandsInterface.class);
+        mMockPhone = Mockito.mock(GsmCdmaPhone.class);
+        mMockImsManager = Mockito.mock(ImsManager.class);
+        mMockResources = Mockito.mock(Resources.class);
+
+        mPhoneslist = new Phone[] {mMockPhone};
+        mTpdu = HexDump.hexStringToByteArray(mPdu);
+
+        //Use reflection to mock
+        replaceInstance(PhoneFactory.class, "sPhones", null, mPhoneslist);
+        replaceInstance(PhoneFactory.class, "sPhone", null, mMockPhone);
+
+        mPhoneObj = PhoneFactory.getPhone(mSlotId);
+        assertNotNull(mPhoneObj);
+        mPhoneId = mPhoneObj.getPhoneId();
+        doReturn(mSmsStats).when(mPhoneObj).getSmsStats();
+
+        //new UsimDataDownloadHandlerThread(TAG).start();
+        //waitUntilReady();
+        mUsimDataDownloadHandler = new UsimDataDownloadHandler(mMockCi, mPhoneId);
+        mUsimDataDownloadHandler.setImsManager(mMockImsManager);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mUsimDataDownloadHandler = null;
+        mPhoneslist = null;
+        super.tearDown();
+    }
+
+    @Test
+    public void sendEnvelopeForException() throws Exception {
+        setSmsPPSimConfig(false);
+        com.android.internal.telephony.gsm.SmsMessage sms =
+                com.android.internal.telephony.gsm.SmsMessage.createFromPdu(mTpdu);
+
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(1);
+                    AsyncResult.forMessage(response, null, new CommandException(
+                                CommandException.Error.OPERATION_NOT_ALLOWED));
+                    response.sendToTarget();
+                    return null;
+                })
+                .when(mMockCi)
+                .sendEnvelopeWithStatus(anyString(), any(Message.class));
+
+        mUsimDataDownloadHandler.startDataDownload(sms,
+                 InboundSmsHandler.SOURCE_INJECTED_FROM_IMS, mToken);
+        processAllMessages();
+        verify(mMockCi).acknowledgeLastIncomingGsmSms(false,
+                CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR, null);
+
+        setSmsPPSimConfig(true);
+        mUsimDataDownloadHandler.startDataDownload(sms,
+                 InboundSmsHandler.SOURCE_INJECTED_FROM_IMS, mToken);
+        processAllMessages();
+        //mTpdu[9] holds messageReference value
+        verify(mMockImsManager).acknowledgeSms(mToken, mTpdu[9],
+                ImsSmsImplBase.DELIVER_STATUS_ERROR_GENERIC);
+    }
+
+    @Test
+    public void sendEnvelopeDataDownloadSuccess() throws Exception {
+        setSmsPPSimConfig(true);
+        com.android.internal.telephony.gsm.SmsMessage sms =
+                com.android.internal.telephony.gsm.SmsMessage.createFromPdu(mTpdu);
+        mSmsAckPdu = createSmsAckPdu(true, mEnvelope, sms);
+
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(1);
+                    IccIoResult iir = new IccIoResult(0x90, 0x00,
+                            IccUtils.hexStringToBytes(mEnvelope));
+                    AsyncResult.forMessage(response, iir, null);
+                    response.sendToTarget();
+                    return null;
+                })
+                .when(mMockCi)
+                .sendEnvelopeWithStatus(anyString(), any(Message.class));
+
+        mUsimDataDownloadHandler.startDataDownload(sms,
+                 InboundSmsHandler.SOURCE_INJECTED_FROM_IMS, mToken);
+        processAllMessages();
+        //mTpdu[9] holds messageReference value
+        verify(mMockImsManager).acknowledgeSms(mToken, mTpdu[9],
+                ImsSmsImplBase.DELIVER_STATUS_OK, mSmsAckPdu);
+
+        setSmsPPSimConfig(false);
+        mUsimDataDownloadHandler.startDataDownload(sms,
+                 InboundSmsHandler.SOURCE_INJECTED_FROM_IMS, mToken);
+        processAllMessages();
+        verify(mMockCi).acknowledgeIncomingGsmSmsWithPdu(true,
+                IccUtils.bytesToHexString(mSmsAckPdu), null);
+    }
+
+    @Test
+    public void sendEnvelopeDataDownloadFailed() throws Exception {
+        setSmsPPSimConfig(false);
+        com.android.internal.telephony.gsm.SmsMessage sms =
+                com.android.internal.telephony.gsm.SmsMessage.createFromPdu(mTpdu);
+
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(1);
+                    IccIoResult iir = new IccIoResult(0x93, 0x00,
+                            IccUtils.hexStringToBytes(mEnvelope));
+                    AsyncResult.forMessage(response, iir, null);
+                    response.sendToTarget();
+                    return null;
+                })
+                .when(mMockCi)
+                .sendEnvelopeWithStatus(anyString(), any(Message.class));
+
+        mUsimDataDownloadHandler.startDataDownload(sms,
+                 InboundSmsHandler.SOURCE_INJECTED_FROM_IMS, mToken);
+        processAllMessages();
+        verify(mMockCi).acknowledgeLastIncomingGsmSms(false,
+                CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_APP_TOOLKIT_BUSY, null);
+
+        setSmsPPSimConfig(true);
+        mUsimDataDownloadHandler.startDataDownload(sms,
+                 InboundSmsHandler.SOURCE_INJECTED_FROM_IMS, mToken);
+        processAllMessages();
+        //mTpdu[9] holds messageReference value
+        verify(mMockImsManager).acknowledgeSms(mToken, mTpdu[9],
+                ImsSmsImplBase.DELIVER_STATUS_ERROR_GENERIC);
+    }
+
+    @Test
+    public void sendEnvelopeForSw1_62() throws Exception {
+        setSmsPPSimConfig(false);
+        com.android.internal.telephony.gsm.SmsMessage sms =
+                com.android.internal.telephony.gsm.SmsMessage.createFromPdu(mTpdu);
+        mSmsAckPdu = createSmsAckPdu(false, mEnvelope, sms);
+
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(1);
+                    IccIoResult iir = new IccIoResult(0x62, 0x63,
+                            IccUtils.hexStringToBytes(mEnvelope));
+                    AsyncResult.forMessage(response, iir, null);
+                    response.sendToTarget();
+                    return null;
+                })
+                .when(mMockCi)
+                .sendEnvelopeWithStatus(anyString(), any(Message.class));
+
+        mUsimDataDownloadHandler.startDataDownload(sms,
+                 InboundSmsHandler.SOURCE_INJECTED_FROM_IMS, mToken);
+        processAllMessages();
+        verify(mMockCi).acknowledgeIncomingGsmSmsWithPdu(false,
+                IccUtils.bytesToHexString(mSmsAckPdu), null);
+
+        setSmsPPSimConfig(true);
+        mUsimDataDownloadHandler.startDataDownload(sms,
+                 InboundSmsHandler.SOURCE_INJECTED_FROM_IMS, mToken);
+        processAllMessages();
+        //mTpdu[9] holds messageReference value
+        verify(mMockImsManager).acknowledgeSms(mToken, mTpdu[9],
+                ImsSmsImplBase.DELIVER_STATUS_OK, mSmsAckPdu);
+    }
+
+    @Test
+    public void smsCompleteForException() throws Exception {
+        setSmsPPSimConfig(false);
+
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(3);
+                    AsyncResult.forMessage(response, null, new CommandException(
+                                CommandException.Error.OPERATION_NOT_ALLOWED));
+                    response.sendToTarget();
+                    return null;
+                })
+                .when(mMockCi)
+                .writeSmsToSim(anyInt(), anyString(), anyString(), any(Message.class));
+
+        int[] responseInfo = {mSmsSource, mTpdu[9], mToken};
+        Message msg = mUsimDataDownloadHandler.obtainMessage(3 /* EVENT_WRITE_SMS_COMPLETE */,
+                responseInfo);
+        AsyncResult.forMessage(msg, null, new CommandException(
+                                CommandException.Error.OPERATION_NOT_ALLOWED));
+        mUsimDataDownloadHandler.handleMessage(msg);
+        verify(mMockCi).acknowledgeLastIncomingGsmSms(false,
+                CommandsInterface.GSM_SMS_FAIL_CAUSE_UNSPECIFIED_ERROR, null);
+
+        setSmsPPSimConfig(true);
+        mUsimDataDownloadHandler.handleMessage(msg);
+        //mTpdu[9] holds messageReference value
+        verify(mMockImsManager).acknowledgeSms(mToken, mTpdu[9],
+                ImsSmsImplBase.DELIVER_STATUS_ERROR_GENERIC);
+    }
+
+    @Test
+    public void smsComplete() throws Exception {
+        setSmsPPSimConfig(true);
+
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(3);
+                    IccIoResult iir = new IccIoResult(0x90, 0x00,
+                            IccUtils.hexStringToBytes(mEnvelope));
+                    AsyncResult.forMessage(response, iir, null);
+                    response.sendToTarget();
+                    return null;
+                })
+                .when(mMockCi)
+                .writeSmsToSim(anyInt(), anyString(), anyString(), any(Message.class));
+
+        int[] responseInfo = {mSmsSource, mTpdu[9], mToken};
+        Message msg = mUsimDataDownloadHandler.obtainMessage(3 /* EVENT_WRITE_SMS_COMPLETE */,
+                responseInfo);
+        AsyncResult.forMessage(msg, null, null);
+        mUsimDataDownloadHandler.handleMessage(msg);
+        //mTpdu[9] holds messageReference value
+        verify(mMockImsManager).acknowledgeSms(mToken, mTpdu[9], ImsSmsImplBase.DELIVER_STATUS_OK);
+
+        setSmsPPSimConfig(false);
+        mUsimDataDownloadHandler.handleMessage(msg);
+        verify(mMockCi).acknowledgeLastIncomingGsmSms(true, 0, null);
+    }
+
+    @Test
+    public void failureEnvelopeResponse() throws Exception {
+        setSmsPPSimConfig(false);
+        com.android.internal.telephony.gsm.SmsMessage sms =
+                com.android.internal.telephony.gsm.SmsMessage.createFromPdu(mTpdu);
+
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(1);
+                    IccIoResult iir = new IccIoResult(0x62, 0x63,
+                            IccUtils.hexStringToBytes(null)); //ForNullResponseBytes
+                    AsyncResult.forMessage(response, iir, null);
+                    response.sendToTarget();
+                    return null;
+                })
+                .when(mMockCi)
+                .sendEnvelopeWithStatus(anyString(), any(Message.class));
+
+        mUsimDataDownloadHandler.startDataDownload(sms,
+                 InboundSmsHandler.SOURCE_INJECTED_FROM_IMS, mToken);
+        processAllMessages();
+        verify(mMockCi).acknowledgeLastIncomingGsmSms(false,
+                CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR, null);
+
+        setSmsPPSimConfig(true);
+        mUsimDataDownloadHandler.startDataDownload(sms,
+                 InboundSmsHandler.SOURCE_INJECTED_FROM_IMS, mToken);
+        processAllMessages();
+        //mTpdu[9] holds messageReference value
+        verify(mMockImsManager).acknowledgeSms(mToken, mTpdu[9],
+                ImsSmsImplBase.DELIVER_STATUS_ERROR_GENERIC);
+    }
+
+    @Test
+    public void successEnvelopeResponse() throws Exception {
+        setSmsPPSimConfig(false);
+        com.android.internal.telephony.gsm.SmsMessage sms =
+                com.android.internal.telephony.gsm.SmsMessage.createFromPdu(mTpdu);
+
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(1);
+                    IccIoResult iir = new IccIoResult(0x90, 0x00,
+                            IccUtils.hexStringToBytes(null)); //ForNullResponseBytes
+                    AsyncResult.forMessage(response, iir, null);
+                    response.sendToTarget();
+                    return null;
+                })
+                .when(mMockCi)
+                .sendEnvelopeWithStatus(anyString(), any(Message.class));
+
+        mUsimDataDownloadHandler.startDataDownload(sms,
+                 InboundSmsHandler.SOURCE_INJECTED_FROM_IMS, mToken);
+        processAllMessages();
+        verify(mMockCi).acknowledgeLastIncomingGsmSms(true, 0, null);
+
+        setSmsPPSimConfig(true);
+        mUsimDataDownloadHandler.startDataDownload(sms,
+                 InboundSmsHandler.SOURCE_INJECTED_FROM_IMS, mToken);
+        processAllMessages();
+        //mTpdu[9] holds messageReference value
+        verify(mMockImsManager).acknowledgeSms(mToken, mTpdu[9], ImsSmsImplBase.DELIVER_STATUS_OK);
+    }
+
+    //To set "config_smppsim_response_via_ims" for testing purpose
+    private void setSmsPPSimConfig(boolean config) {
+        mUsimDataDownloadHandler.setResourcesForTest(mMockResources);
+        doReturn(config).when(mMockResources).getBoolean(
+                com.android.internal.R.bool.config_smppsim_response_via_ims);
+    }
+
+    private byte[] createSmsAckPdu(boolean success, String envelope, SmsMessage smsMessage) {
+        byte[] responseBytes = IccUtils.hexStringToBytes(envelope);
+        int dcs = 0x00;
+        int pid = smsMessage.getProtocolIdentifier();
+        byte[] smsAckPdu;
+        int index = 0;
+        if (success) {
+            smsAckPdu = new byte[responseBytes.length + 5];
+            smsAckPdu[index++] = 0x00;
+            smsAckPdu[index++] = 0x07;
+        } else {
+            smsAckPdu = new byte[responseBytes.length + 6];
+            smsAckPdu[index++] = 0x00;
+            smsAckPdu[index++] = (byte)
+                    CommandsInterface.GSM_SMS_FAIL_CAUSE_USIM_DATA_DOWNLOAD_ERROR;
+            smsAckPdu[index++] = 0x07;
+        }
+
+        smsAckPdu[index++] = (byte) pid;
+        smsAckPdu[index++] = (byte) dcs;
+
+        if (((dcs & 0x8C) == 0x00) || ((dcs & 0xF4) == 0xF0)) {
+            int septetCount = responseBytes.length * 8 / 7;
+            smsAckPdu[index++] = (byte) septetCount;
+        } else {
+            smsAckPdu[index++] = (byte) responseBytes.length;
+        }
+
+        System.arraycopy(responseBytes, 0, smsAckPdu, index, responseBytes.length);
+        return smsAckPdu;
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsEnablementTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsEnablementTrackerTest.java
new file mode 100644
index 0000000..c6f45d9
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsEnablementTrackerTest.java
@@ -0,0 +1,812 @@
+/*
+ * Copyright (C) 2022 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.ims;
+
+import static com.android.internal.telephony.ims.ImsEnablementTracker.COMMAND_DISABLE_MSG;
+import static com.android.internal.telephony.ims.ImsEnablementTracker.COMMAND_ENABLE_MSG;
+import static com.android.internal.telephony.ims.ImsEnablementTracker.COMMAND_RESETTING_DONE;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.telephony.ims.aidl.IImsServiceController;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+/**
+ * Unit tests for ImsEnablementTracker
+ */
+@RunWith(AndroidJUnit4.class)
+public class ImsEnablementTrackerTest extends ImsTestBase {
+
+    // default timeout values(millisecond) for handler working
+    private static final int DEFAULT_TIMEOUT = 100;
+
+    // default delay values(millisecond) for handler working
+    private static final int DEFAULT_DELAY = 150;
+
+    private static final int SLOT_1 = 0;
+    private static final int SUB_1 = 11;
+
+    private static final int SLOT_2 = 1;
+    private static final int SUB_2 = 22;
+    private static final long TEST_REQUEST_THROTTLE_TIME_MS = 3000L;
+
+    // Mocked classes
+    @Mock
+    IImsServiceController mMockServiceControllerBinder;
+
+    private TestableImsEnablementTracker mTracker;
+    private Handler mHandler;
+
+    private static long sLastImsOperationTimeMs = 0L;
+
+    private static class TestableImsEnablementTracker extends ImsEnablementTracker {
+        private ImsEnablementTrackerTest mOwner;
+
+        TestableImsEnablementTracker(Looper looper, IImsServiceController controller, int state,
+                int numSlots, ImsEnablementTrackerTest owner) {
+            super(looper, controller, state, numSlots);
+            mOwner = owner;
+        }
+
+        @Override
+        protected long getLastOperationTimeMillis() {
+            return ImsEnablementTrackerTest.sLastImsOperationTimeMs;
+        }
+    }
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @After
+    @Override
+    public void tearDown() throws Exception {
+        // Make sure the handler is empty before finishing the test.
+        waitForHandlerAction(mHandler, TEST_REQUEST_THROTTLE_TIME_MS);
+
+        mTracker = null;
+        super.tearDown();
+    }
+
+    @SmallTest
+    @Test
+    public void testEnableCommandInDefaultState() throws RemoteException {
+        // Verify that when the enable command is received in the Default state and enableIms
+        // is called.
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_DEFAULT, 1, 0);
+        mHandler = mTracker.getHandler(SLOT_1);
+         // Wait for a while for the state machine to be ready.
+        waitForHandler(mHandler);
+
+        mTracker.enableIms(SLOT_1, SUB_1);
+        waitForHandler(mHandler);
+
+        verify(mMockServiceControllerBinder).enableIms(eq(SLOT_1), eq(SUB_1));
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED));
+    }
+
+    @SmallTest
+    @Test
+    public void testDisableCommandInDefaultState() throws RemoteException {
+        // Verify that when the disable command is received in the Default state and disableIms
+        // is called.
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_DEFAULT, 1, 0);
+        mHandler = mTracker.getHandler(SLOT_1);
+        // Wait for a while for the state machine to be ready.
+        waitForHandler(mHandler);
+
+        mTracker.disableIms(SLOT_1, SUB_1);
+        waitForHandler(mHandler);
+
+        verify(mMockServiceControllerBinder).disableIms(eq(SLOT_1), eq(SUB_1));
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLED));
+    }
+
+    @SmallTest
+    @Test
+    public void testResetCommandInDefaultState() throws RemoteException {
+        // Verify that when reset command is received in the Default state, it should be ignored.
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_DEFAULT, 1, 0);
+        mHandler = mTracker.getHandler(SLOT_1);
+        // Wait for a while for the state machine to be ready.
+        waitForHandler(mHandler);
+
+        mTracker.resetIms(SLOT_1, SUB_1);
+        waitForHandler(mHandler);
+
+        verify(mMockServiceControllerBinder).resetIms(eq(SLOT_1), eq(SUB_1));
+        waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(),
+                mTracker.getRemainThrottleTime() + DEFAULT_DELAY);
+
+        verify(mMockServiceControllerBinder).enableIms(eq(SLOT_1), eq(SUB_1));
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED));
+    }
+
+    @SmallTest
+    @Test
+    public void testEnableCommandInEnabledState() throws RemoteException {
+        // Verify that received the enable command is not handle in the Enabled state,
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_ENABLED, 1, 0);
+        mHandler = mTracker.getHandler(SLOT_1);
+        // Wait for a while for the state machine to be ready.
+        waitForHandler(mHandler);
+
+        mTracker.enableIms(SLOT_1, SUB_1);
+        waitForHandler(mHandler);
+
+        verify(mMockServiceControllerBinder, never()).enableIms(eq(SLOT_1), eq(SUB_1));
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED));
+    }
+
+    @SmallTest
+    @Test
+    public void testDisableCommandInEnabledState() throws RemoteException {
+        // Verify that when the disable command is received in the Enabled state and disableIms
+        // is called.
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_ENABLED, 1, 0);
+        mHandler = mTracker.getHandler(SLOT_1);
+        // Wait for a while for the state machine to be ready.
+        waitForHandler(mHandler);
+
+        mTracker.disableIms(SLOT_1, SUB_1);
+        waitForHandler(mHandler);
+
+        verify(mMockServiceControllerBinder).disableIms(eq(SLOT_1), eq(SUB_1));
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLED));
+    }
+
+    @SmallTest
+    @Test
+    public void testResetCommandInEnabledState() throws RemoteException {
+        // Verify that when the reset command is received in the Enabled state and disableIms
+        // and enableIms are called.
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_ENABLED, 1, 0);
+        mHandler = mTracker.getHandler(SLOT_1);
+        // Wait for a while for the state machine to be ready.
+        waitForHandler(mHandler);
+
+        mTracker.resetIms(SLOT_1, SUB_1);
+        waitForHandler(mHandler);
+        verify(mMockServiceControllerBinder).resetIms(eq(SLOT_1), eq(SUB_1));
+
+        waitForHandlerActionDelayed(mHandler, TEST_REQUEST_THROTTLE_TIME_MS,
+                TEST_REQUEST_THROTTLE_TIME_MS + DEFAULT_DELAY);
+
+        verify(mMockServiceControllerBinder, never()).disableIms(eq(SLOT_1), eq(SUB_1));
+        verify(mMockServiceControllerBinder, times(1)).enableIms(eq(SLOT_1), eq(SUB_1));
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED));
+    }
+
+    @SmallTest
+    @Test
+    public void testDisableCommandInDisabledState() throws RemoteException {
+        // Verify that when disable command is received in the Disabled state, it should be ignored.
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_DISABLED, 1,
+                System.currentTimeMillis());
+        mHandler = mTracker.getHandler(SLOT_1);
+        // Wait for a while for the state machine to be ready.
+        waitForHandler(mHandler);
+
+        mTracker.disableIms(SLOT_1, SUB_1);
+        waitForHandler(mHandler);
+
+        verifyZeroInteractions(mMockServiceControllerBinder);
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLED));
+    }
+
+    @SmallTest
+    @Test
+    public void testEnableCommandInDisabledState() throws RemoteException {
+        // Verify that when the enable command is received in the Disabled state and enableIms
+        // is called.
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_DISABLED, 1, 0);
+        mHandler = mTracker.getHandler(SLOT_1);
+        // Wait for a while for the state machine to be ready.
+        waitForHandler(mHandler);
+
+        mTracker.enableIms(SLOT_1, SUB_1);
+        waitForHandler(mHandler);
+
+        verify(mMockServiceControllerBinder).enableIms(eq(SLOT_1), eq(SUB_1));
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED));
+    }
+
+    @SmallTest
+    @Test
+    public void testEnableCommandWithoutTimeoutInDisableState() throws RemoteException {
+        // Verify that when the enable command is received in the Disabled state. After throttle
+        // time expired, the enableIms is called.
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_DISABLED, 1,
+                System.currentTimeMillis());
+        mHandler = mTracker.getHandler(SLOT_1);
+        // Wait for a while for the state machine to be ready.
+        waitForHandler(mHandler);
+
+        mTracker.enableIms(SLOT_1, SUB_1);
+        waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(),
+                mTracker.getRemainThrottleTime() + DEFAULT_DELAY);
+
+        verify(mMockServiceControllerBinder).enableIms(eq(SLOT_1), eq(SUB_1));
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED));
+    }
+
+    @SmallTest
+    @Test
+    public void testResetCommandInDisabledState() throws RemoteException {
+        // Verify that the reset command is received in the Disabled state and it`s not handled.
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_DISABLED, 1, 0);
+        mHandler = mTracker.getHandler(SLOT_1);
+        // Wait for a while for the state machine to be ready.
+        waitForHandler(mHandler);
+
+        mTracker.resetIms(SLOT_1, SUB_1);
+        waitForHandler(mHandler);
+        verify(mMockServiceControllerBinder).resetIms(eq(SLOT_1), eq(SUB_1));
+
+        // The disableIms was called. So set the last operation time to current.
+        waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(),
+                mTracker.getRemainThrottleTime() + DEFAULT_DELAY);
+
+        verify(mMockServiceControllerBinder, times(1)).enableIms(eq(SLOT_1), eq(SUB_1));
+        verify(mMockServiceControllerBinder, never()).disableIms(eq(SLOT_1), eq(SUB_1));
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED));
+    }
+
+    @SmallTest
+    @Test
+    public void testEnableCommandInDisablingState() throws RemoteException {
+        // Verify that when enable command is received in the Disabling state, it should be ignored.
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_DISABLING, 1,
+                System.currentTimeMillis());
+        mHandler = mTracker.getHandler(SLOT_1);
+        // Wait for a while for the state machine to be ready.
+        waitForHandler(mHandler);
+
+        mTracker.enableIms(SLOT_1, SUB_1);
+        waitForHandler(mHandler);
+
+        verifyZeroInteractions(mMockServiceControllerBinder);
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED));
+    }
+
+    @SmallTest
+    @Test
+    public void testDisablingMessageInDisablingState() throws RemoteException {
+        // Verify that when the internal disable message is received in the Disabling state and
+        // disableIms is called.
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_DISABLING, 1,
+                System.currentTimeMillis());
+        mHandler = mTracker.getHandler(SLOT_1);
+
+        waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(),
+                mTracker.getRemainThrottleTime() + DEFAULT_DELAY);
+
+        verify(mMockServiceControllerBinder).disableIms(anyInt(), anyInt());
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLED));
+    }
+
+    @SmallTest
+    @Test
+    public void testResetCommandInDisablingState() throws RemoteException {
+        // Verify when the reset command is received in the Disabling state the disableIms and
+        // enableIms are called.
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_DISABLING, 1,
+                System.currentTimeMillis());
+        mHandler = mTracker.getHandler(SLOT_1);
+        // Wait for a while for the state machine to be ready.
+        waitForHandler(mHandler);
+
+        mTracker.resetIms(SLOT_1, SUB_1);
+        waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(),
+                mTracker.getRemainThrottleTime() + DEFAULT_DELAY);
+
+        verify(mMockServiceControllerBinder).resetIms(eq(SLOT_1), eq(SUB_1));
+
+        // The disableIms was called. So set the last operation time to current.
+        waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(),
+                mTracker.getRemainThrottleTime() + DEFAULT_DELAY);
+
+        verify(mMockServiceControllerBinder, times(1)).enableIms(eq(SLOT_1), eq(SUB_1));
+        verify(mMockServiceControllerBinder, never()).disableIms(eq(SLOT_1), eq(SUB_1));
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED));
+    }
+
+    @SmallTest
+    @Test
+    public void testEnablingMessageInEnablingState() throws RemoteException {
+        // Verify that when the internal enable message is received in the Enabling state and
+        // enableIms is called.
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_ENABLING, 1,
+                System.currentTimeMillis());
+        mHandler = mTracker.getHandler(SLOT_1);
+        waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(),
+                mTracker.getRemainThrottleTime() + DEFAULT_DELAY);
+
+        verify(mMockServiceControllerBinder).enableIms(anyInt(), anyInt());
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED));
+    }
+
+    @SmallTest
+    @Test
+    public void testDisableCommandInEnablingState() throws RemoteException {
+        // Verify that when the disable command is received in the Enabling state and
+        // clear pending message and disableIms is not called.
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_ENABLING, 1,
+                System.currentTimeMillis());
+        mHandler = mTracker.getHandler(SLOT_1);
+        // Wait for a while for the state machine to be ready.
+        waitForHandler(mHandler);
+
+        mTracker.disableIms(SLOT_1, SUB_1);
+        waitForHandler(mHandler);
+
+        verify(mMockServiceControllerBinder, never()).disableIms(eq(SLOT_1), eq(SUB_1));
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLED));
+    }
+
+    @SmallTest
+    @Test
+    public void testResetCommandWithEnablingState() throws RemoteException {
+        // Verify that when reset command is received in the Enabling state, it should be ignored.
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_ENABLING, 1,
+                System.currentTimeMillis());
+        mHandler = mTracker.getHandler(SLOT_1);
+        // Wait for a while for the state machine to be ready.
+        waitForHandler(mHandler);
+
+        mTracker.resetIms(SLOT_1, SUB_1);
+        waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(),
+                mTracker.getRemainThrottleTime() + DEFAULT_DELAY);
+
+        verify(mMockServiceControllerBinder).resetIms(eq(SLOT_1), eq(SUB_1));
+
+        // The disableIms was called. So set the last operation time to current.
+        waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(),
+                mTracker.getRemainThrottleTime() + DEFAULT_DELAY);
+
+        verify(mMockServiceControllerBinder, times(1)).enableIms(eq(SLOT_1), eq(SUB_1));
+        verify(mMockServiceControllerBinder, never()).disableIms(eq(SLOT_1), eq(SUB_1));
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED));
+    }
+
+    @SmallTest
+    @Test
+    public void testEnableCommandInResettingState() throws RemoteException {
+        // Verify that when the enable command is received in the Resetting state and
+        // enableIms is not called.
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_RESETTING, 1,
+                System.currentTimeMillis());
+        mHandler = mTracker.getHandler(SLOT_1);
+        // Wait for a while for the state machine to be ready.
+        waitForHandler(mHandler);
+
+        mTracker.enableIms(SLOT_1, SUB_1);
+        waitForHandler(mHandler);
+
+        verifyZeroInteractions(mMockServiceControllerBinder);
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_RESETTING));
+    }
+
+    @SmallTest
+    @Test
+    public void testDisableCommandInResettingState() throws RemoteException {
+        // Verify that when the disable command is received in the Resetting state and
+        // disableIms is called.
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_RESETTING, 1,
+                System.currentTimeMillis());
+        mHandler = mTracker.getHandler(SLOT_1);
+        // Wait for a while for the state machine to be ready.
+        waitForHandler(mHandler);
+
+        mTracker.disableIms(SLOT_1, SUB_1);
+        waitForHandler(mHandler);
+
+        verifyZeroInteractions(mMockServiceControllerBinder);
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_RESETTING));
+    }
+
+    @SmallTest
+    @Test
+    public void testResettingMessageInResettingState() throws RemoteException {
+        // Verify that when the internal reset message is received in the Resetting state and
+        // disableIms and enableIms are called.
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_RESETTING, 1,
+                System.currentTimeMillis());
+        mHandler = mTracker.getHandler(SLOT_1);
+
+        // Wait for the throttle time.
+        waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(),
+                mTracker.getRemainThrottleTime() + DEFAULT_DELAY);
+
+        verify(mMockServiceControllerBinder).resetIms(anyInt(), anyInt());
+
+        // Set the last operation time to current to verify the message with delay.
+        waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(),
+                mTracker.getRemainThrottleTime() + DEFAULT_DELAY);
+
+        verify(mMockServiceControllerBinder, times(1)).enableIms(eq(SLOT_1), anyInt());
+        verify(mMockServiceControllerBinder, never()).disableIms(eq(SLOT_1), anyInt());
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED));
+    }
+
+    @SmallTest
+    @Test
+    public void testDisableEnableMessageInResettingState() throws RemoteException {
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_RESETTING, 1,
+                System.currentTimeMillis());
+        mHandler = mTracker.getHandler(SLOT_1);
+        // Wait for a while for the state machine to be ready.
+        waitForHandler(mHandler);
+
+        // Simulation.
+        // In Resetting state, disableIms() called before doing resetIms().
+        // After doing resetIms(), during the throttle time(before doing disableIms()),
+        // enableIms() called. Finally skip disableIms() and do enableIms().
+        mHandler.removeMessages(COMMAND_RESETTING_DONE);
+        mHandler.sendMessage(mHandler.obtainMessage(COMMAND_DISABLE_MSG, SLOT_1, SUB_1));
+        mHandler.sendMessage(mHandler.obtainMessage(COMMAND_RESETTING_DONE, SLOT_1, SUB_1));
+        mHandler.sendMessage(mHandler.obtainMessage(COMMAND_ENABLE_MSG, SLOT_1, SUB_1));
+
+        waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(),
+                mTracker.getRemainThrottleTime() + DEFAULT_DELAY);
+
+        verify(mMockServiceControllerBinder, times(1)).resetIms(eq(SLOT_1), eq(SUB_1));
+        verify(mMockServiceControllerBinder, times(1)).enableIms(eq(SLOT_1), eq(SUB_1));
+        verify(mMockServiceControllerBinder, never()).disableIms(eq(SLOT_1), anyInt());
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED));
+    }
+
+    @SmallTest
+    @Test
+    public void testEnableDisableMessageInResettingState() throws RemoteException {
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_RESETTING, 1,
+                System.currentTimeMillis());
+        mHandler = mTracker.getHandler(SLOT_1);
+        // Wait for a while for the state machine to be ready.
+        waitForHandler(mHandler);
+
+        // Simulation.
+        // In Resetting state, enableIms() called before doing resetIms().
+        // After doing resetIms(), during the throttle time(before doing enableIms()),
+        // disableIms() called. Finally skip enableIms() and do disableIms().
+        mHandler.removeMessages(COMMAND_RESETTING_DONE);
+        mHandler.sendMessage(mHandler.obtainMessage(COMMAND_ENABLE_MSG, SLOT_1, SUB_1));
+        mHandler.sendMessage(mHandler.obtainMessage(COMMAND_RESETTING_DONE, SLOT_1, SUB_1));
+        mHandler.sendMessage(mHandler.obtainMessage(COMMAND_DISABLE_MSG, SLOT_1, SUB_1));
+
+        waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(),
+                mTracker.getRemainThrottleTime() + DEFAULT_DELAY);
+
+        verify(mMockServiceControllerBinder, times(1)).resetIms(eq(SLOT_1), eq(SUB_1));
+        verify(mMockServiceControllerBinder, times(1)).disableIms(eq(SLOT_1), eq(SUB_1));
+        verify(mMockServiceControllerBinder, never()).enableIms(eq(SLOT_1), eq(SUB_1));
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLED));
+    }
+
+    @SmallTest
+    @Test
+    public void testRepeatEnableMessageInResettingState() throws RemoteException {
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_RESETTING, 1,
+                System.currentTimeMillis());
+        mHandler = mTracker.getHandler(SLOT_1);
+        // Wait for a while for the state machine to be ready.
+        waitForHandler(mHandler);
+
+        // Simulation.
+        // In Resetting state, enableIms(), disableIms() are called repeatedly.
+        // After doing resetIms(), latest enableIms() should perform.
+        mHandler.removeMessages(COMMAND_RESETTING_DONE);
+        mHandler.sendMessage(mHandler.obtainMessage(COMMAND_RESETTING_DONE, SLOT_1, SUB_1));
+        mHandler.sendMessage(mHandler.obtainMessage(COMMAND_DISABLE_MSG, SLOT_1, SUB_1));
+        mHandler.sendMessage(mHandler.obtainMessage(COMMAND_ENABLE_MSG, SLOT_1, SUB_1));
+        mHandler.sendMessage(mHandler.obtainMessage(COMMAND_DISABLE_MSG, SLOT_1, SUB_1));
+        mHandler.sendMessage(mHandler.obtainMessage(COMMAND_ENABLE_MSG, SLOT_1, SUB_1));
+
+        waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(),
+                mTracker.getRemainThrottleTime() + DEFAULT_DELAY);
+
+        verify(mMockServiceControllerBinder, times(1)).resetIms(eq(SLOT_1), eq(SUB_1));
+        verify(mMockServiceControllerBinder, times(1)).enableIms(eq(SLOT_1), eq(SUB_1));
+        verify(mMockServiceControllerBinder, never()).disableIms(eq(SLOT_1), eq(SUB_1));
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED));
+    }
+
+    @SmallTest
+    @Test
+    public void testRepeatDisableMessageInResettingState() throws RemoteException {
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_RESETTING, 1,
+                System.currentTimeMillis());
+        mHandler = mTracker.getHandler(SLOT_1);
+        // Wait for a while for the state machine to be ready.
+        waitForHandler(mHandler);
+
+        // Simulation.
+        // In Resetting state, enableIms(), disableIms() are called repeatedly.
+        // After doing resetIms(), latest disableIms() should perform.
+        mHandler.removeMessages(COMMAND_RESETTING_DONE);
+        mHandler.sendMessage(mHandler.obtainMessage(COMMAND_RESETTING_DONE, SLOT_1, SUB_1));
+        mHandler.sendMessage(mHandler.obtainMessage(COMMAND_ENABLE_MSG, SLOT_1, SUB_1));
+        mHandler.sendMessage(mHandler.obtainMessage(COMMAND_DISABLE_MSG, SLOT_1, SUB_1));
+        mHandler.sendMessage(mHandler.obtainMessage(COMMAND_ENABLE_MSG, SLOT_1, SUB_1));
+        mHandler.sendMessage(mHandler.obtainMessage(COMMAND_DISABLE_MSG, SLOT_1, SUB_1));
+
+        waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(),
+                mTracker.getRemainThrottleTime() + DEFAULT_DELAY);
+
+        verify(mMockServiceControllerBinder, times(1)).resetIms(eq(SLOT_1), eq(SUB_1));
+        verify(mMockServiceControllerBinder, times(1)).disableIms(eq(SLOT_1), eq(SUB_1));
+        verify(mMockServiceControllerBinder, never()).enableIms(eq(SLOT_1), eq(SUB_1));
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLED));
+    }
+
+    @SmallTest
+    @Test
+    public void testConsecutiveCommandInEnabledState() throws RemoteException {
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_ENABLED, 1,
+                System.currentTimeMillis());
+        mHandler = mTracker.getHandler(SLOT_1);
+        // Wait for a while for the state machine to be ready.
+        waitForHandler(mHandler);
+
+        mTracker.enableIms(SLOT_1, SUB_1);
+        waitForHandler(mHandler);
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED));
+
+        // Set the last operation time to current to verify the message with delay.
+        sLastImsOperationTimeMs = System.currentTimeMillis();
+        mTracker.disableIms(SLOT_1, SUB_1);
+        waitForHandler(mHandler);
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLING));
+
+        mTracker.enableIms(SLOT_1, SUB_1);
+        waitForHandler(mHandler);
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED));
+
+        mTracker.disableIms(SLOT_1, SUB_1);
+        waitForHandler(mHandler);
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLING));
+
+        mTracker.disableIms(SLOT_1, SUB_1);
+        waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(),
+                mTracker.getRemainThrottleTime() + DEFAULT_DELAY);
+
+        verify(mMockServiceControllerBinder, times(1)).disableIms(eq(SLOT_1), eq(SUB_1));
+        verify(mMockServiceControllerBinder, never()).resetIms(eq(SLOT_1), eq(SUB_1));
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLED));
+    }
+
+    @SmallTest
+    @Test
+    public void testConsecutiveCommandInDisabledState() throws RemoteException {
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_DISABLED, 1,
+                System.currentTimeMillis());
+        mHandler = mTracker.getHandler(SLOT_1);
+        // Wait for a while for the state machine to be ready.
+        waitForHandler(mHandler);
+
+        // Set the last operation time to current to verify the message with delay.
+        sLastImsOperationTimeMs = System.currentTimeMillis();
+        mTracker.enableIms(SLOT_1, SUB_1);
+        waitForHandler(mHandler);
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLING));
+
+        mTracker.disableIms(SLOT_1, SUB_1);
+        waitForHandler(mHandler);
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLED));
+
+        mTracker.resetIms(SLOT_1, SUB_1);
+        waitForHandler(mHandler);
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_RESETTING));
+
+        mTracker.disableIms(SLOT_1, SUB_1);
+        waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(),
+                mTracker.getRemainThrottleTime() + DEFAULT_DELAY);
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLED));
+
+        mTracker.enableIms(SLOT_1, SUB_1);
+        waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(),
+                mTracker.getRemainThrottleTime() + DEFAULT_DELAY);
+
+        verify(mMockServiceControllerBinder, times(1)).enableIms(eq(SLOT_1), eq(SUB_1));
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED));
+    }
+
+    @SmallTest
+    @Test
+    public void testSubIdChangeToInvalidAndEnableCommand() throws RemoteException {
+        // Verify that when the enable command is received in the Default state and enableIms
+        // is called.
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_ENABLED, 1,
+                System.currentTimeMillis());
+        mHandler = mTracker.getHandler(SLOT_1);
+        // Wait for a while for the state machine to be ready.
+        waitForHandler(mHandler);
+
+        mTracker.subIdChangedToInvalid(SLOT_1);
+        waitForHandler(mHandler);
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DEFAULT));
+
+        mTracker.enableIms(SLOT_1, SUB_1);
+        waitForHandler(mHandler);
+        verify(mMockServiceControllerBinder).enableIms(eq(SLOT_1), eq(SUB_1));
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED));
+    }
+
+    @SmallTest
+    @Test
+    public void testEnableCommandWithDifferentSlotId() throws RemoteException {
+        // Verify that when the enable command is received in the Default state and enableIms
+        // is called.
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_DEFAULT, 2,
+                System.currentTimeMillis());
+        mHandler = mTracker.getHandler(SLOT_1);
+        Handler handlerForSlot2 = mTracker.getHandler(SLOT_2);
+        // Wait for a while for the state machine to be ready.
+        waitForHandler(mHandler);
+        waitForHandler(handlerForSlot2);
+
+        mTracker.enableIms(SLOT_1, SUB_1);
+        waitForHandler(mHandler);
+
+        verify(mMockServiceControllerBinder).enableIms(eq(SLOT_1), eq(SUB_1));
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED));
+        assertTrue(mTracker.isState(SLOT_2, mTracker.STATE_IMS_DEFAULT));
+
+        mTracker.enableIms(SLOT_2, SUB_2);
+        waitForHandler(handlerForSlot2);
+
+        verify(mMockServiceControllerBinder).enableIms(eq(SLOT_2), eq(SUB_2));
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED));
+        assertTrue(mTracker.isState(SLOT_2, mTracker.STATE_IMS_ENABLED));
+
+        mTracker.setNumOfSlots(1);
+        sLastImsOperationTimeMs = System.currentTimeMillis();
+        mTracker.disableIms(SLOT_1, SUB_1);
+        waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(),
+                mTracker.getRemainThrottleTime() + DEFAULT_DELAY);
+
+        verify(mMockServiceControllerBinder).disableIms(eq(SLOT_1), eq(SUB_1));
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLED));
+
+        mTracker.setNumOfSlots(2);
+        sLastImsOperationTimeMs = System.currentTimeMillis();
+        mTracker.disableIms(SLOT_2, SUB_2);
+        waitForHandler(handlerForSlot2);
+
+        verify(mMockServiceControllerBinder).disableIms(eq(SLOT_2), eq(SUB_2));
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLED));
+        assertTrue(mTracker.isState(SLOT_2, mTracker.STATE_IMS_DISABLED));
+    }
+
+    @SmallTest
+    @Test
+    public void testEnableCommandInPostResettingState() throws RemoteException {
+        // Verify that when the enable/disable commands are received in the PostResetting state
+        // and final enableIms is called.
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_POSTRESETTING, 1,
+                System.currentTimeMillis());
+        mHandler = mTracker.getHandler(SLOT_1);
+        // Wait for a while for the state machine to be ready.
+        waitForHandler(mHandler);
+
+        // to confirm the slotId, subId for COMMAND_POST_RESETTING_DONE
+        mHandler.removeMessages(mTracker.COMMAND_POST_RESETTING_DONE);
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(mTracker.COMMAND_POST_RESETTING_DONE,
+                SLOT_1, SUB_1), mTracker.getRemainThrottleTime());
+
+        mTracker.enableIms(SLOT_1, SUB_1);
+        mTracker.disableIms(SLOT_1, SUB_1);
+        mTracker.enableIms(SLOT_1, SUB_1);
+        mTracker.disableIms(SLOT_1, SUB_1);
+        mTracker.enableIms(SLOT_1, SUB_1);
+        waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(),
+                mTracker.getRemainThrottleTime() + DEFAULT_DELAY);
+
+        verify(mMockServiceControllerBinder, times(1)).enableIms(eq(SLOT_1), eq(SUB_1));
+        verify(mMockServiceControllerBinder, never()).disableIms(eq(SLOT_1), anyInt());
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED));
+    }
+
+    @SmallTest
+    @Test
+    public void testDisableCommandInPostResettingState() throws RemoteException {
+        // Verify that when the enable/disable commands are received in the PostResetting state
+        // and final disableIms is called.
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_POSTRESETTING, 1,
+                System.currentTimeMillis());
+        mHandler = mTracker.getHandler(SLOT_1);
+        // Wait for a while for the state machine to be ready.
+        waitForHandler(mHandler);
+
+        // to confirm the slotId, subId for COMMAND_POST_RESETTING_DONE
+        mHandler.removeMessages(mTracker.COMMAND_POST_RESETTING_DONE);
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(mTracker.COMMAND_POST_RESETTING_DONE,
+                SLOT_1, SUB_1), mTracker.getRemainThrottleTime());
+
+        mTracker.disableIms(SLOT_1, SUB_1);
+        mTracker.enableIms(SLOT_1, SUB_1);
+        mTracker.disableIms(SLOT_1, SUB_1);
+        mTracker.enableIms(SLOT_1, SUB_1);
+        mTracker.disableIms(SLOT_1, SUB_1);
+        waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(),
+                mTracker.getRemainThrottleTime() + DEFAULT_DELAY);
+
+        verify(mMockServiceControllerBinder, times(1)).disableIms(eq(SLOT_1), eq(SUB_1));
+        verify(mMockServiceControllerBinder, never()).enableIms(eq(SLOT_1), anyInt());
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_DISABLED));
+    }
+
+    @SmallTest
+    @Test
+    public void testResetCommandInPostResettingState() throws RemoteException {
+        // Verify that when the enable/disable/reset commands are received in the PostResetting
+        // state and final enableIms is called without calling resetIms again.
+        mTracker = createTracker(mMockServiceControllerBinder, mTracker.STATE_IMS_POSTRESETTING, 1,
+                System.currentTimeMillis());
+        mHandler = mTracker.getHandler(SLOT_1);
+        // Wait for a while for the state machine to be ready.
+        waitForHandler(mHandler);
+
+        // to confirm the slotId, subId for COMMAND_POST_RESETTING_DONE
+        mHandler.removeMessages(mTracker.COMMAND_POST_RESETTING_DONE);
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(mTracker.COMMAND_POST_RESETTING_DONE,
+                SLOT_1, SUB_1), mTracker.getRemainThrottleTime());
+
+        mTracker.disableIms(SLOT_1, SUB_1);
+        mTracker.enableIms(SLOT_1, SUB_1);
+        mTracker.resetIms(SLOT_1, SUB_1);
+        waitForHandlerActionDelayed(mHandler, mTracker.getRemainThrottleTime(),
+                mTracker.getRemainThrottleTime() + DEFAULT_DELAY);
+
+        verify(mMockServiceControllerBinder, times(1)).enableIms(eq(SLOT_1), eq(SUB_1));
+        verify(mMockServiceControllerBinder, never()).disableIms(eq(SLOT_1), anyInt());
+        verify(mMockServiceControllerBinder, never()).resetIms(eq(SLOT_1), anyInt());
+        assertTrue(mTracker.isState(SLOT_1, mTracker.STATE_IMS_ENABLED));
+    }
+
+    private TestableImsEnablementTracker createTracker(IImsServiceController binder, int state,
+            int numSlots, long initLastOperationTime) {
+        sLastImsOperationTimeMs = initLastOperationTime;
+        TestableImsEnablementTracker tracker = new TestableImsEnablementTracker(
+                Looper.getMainLooper(), binder, state, numSlots, this);
+        return tracker;
+    }
+
+    private void waitForHandler(Handler h) {
+        waitForHandlerActionDelayed(h, DEFAULT_TIMEOUT, DEFAULT_DELAY);
+    }
+}
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 02484ce..abc231a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java
@@ -956,7 +956,7 @@
         carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, ImsFeature.FEATURE_RCS));
         // Assume that there is a CarrierConfig change that kicks off query to carrier service.
         sendCarrierConfigChanged(1, 1);
-        setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 2);
+        setupDynamicQueryFeaturesMultiSim(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 2);
         verify(carrierController).changeImsServiceFeatures(eq(carrierFeatures),
                         any(SparseIntArray.class));
         deviceFeatureSet = convertToHashSet(deviceFeatures, 0);
@@ -2021,6 +2021,7 @@
      */
     private void startBindCarrierConfigAlreadySet() {
         mTestImsResolver.initialize();
+        processAllMessages();
         ArgumentCaptor<BroadcastReceiver> receiversCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
         verify(mMockContext, times(3)).registerReceiver(receiversCaptor.capture(), any());
@@ -2042,6 +2043,7 @@
      */
     private void startBindNoCarrierConfig(int numSlots) {
         mTestImsResolver.initialize();
+        processAllMessages();
         ArgumentCaptor<BroadcastReceiver> receiversCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
         verify(mMockContext, times(3)).registerReceiver(receiversCaptor.capture(), any());
@@ -2068,6 +2070,15 @@
         processAllMessages();
     }
 
+    private void setupDynamicQueryFeaturesMultiSim(ComponentName name,
+            HashSet<ImsFeatureConfiguration.FeatureSlotPair> features, int times) {
+        processAllFutureMessages();
+        // ensure that startQuery was called
+        verify(mMockQueryManager, times(times)).startQuery(eq(name), any(String.class));
+        mDynamicQueryListener.onComplete(name, features);
+        processAllMessages();
+    }
+
     private void setupDynamicQueryFeaturesFailure(ComponentName name, int times) {
         processAllMessages();
         // ensure that startQuery was called
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 b80c6b0..4a3ceaa 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerCompatTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerCompatTest.java
@@ -140,6 +140,8 @@
         SparseIntArray slotIdToSubIdMap = new SparseIntArray();
         slotIdToSubIdMap.put(SLOT_0, SUB_1);
         ServiceConnection conn = bindAndConnectService(slotIdToSubIdMap);
+        long delay = mTestImsServiceController.getRebindDelay();
+        waitForHandlerActionDelayed(mHandler, delay, 2 * delay);
         // add the MMTelFeature
         verify(mMockServiceControllerBinder).createMMTelFeature(SLOT_0);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_0),
@@ -149,6 +151,8 @@
         validateMmTelFeatureContainerExists(SLOT_0);
         // Remove the feature
         conn.onBindingDied(mTestComponentName);
+        delay = REBIND_RETRY.getStartDelay();
+        waitForHandlerActionDelayed(mHandler, delay, 2 * delay);
         verify(mMmTelCompatAdapterSpy).onFeatureRemoved();
         verify(mMockServiceControllerBinder).removeImsFeature(eq(SLOT_0),
                 eq(ImsFeature.FEATURE_MMTEL));
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 113829f..adfc4a3 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java
@@ -634,6 +634,8 @@
 
         conn.onServiceDisconnected(mTestComponentName);
 
+        long delay = mTestImsServiceController.getRebindDelay();
+        waitForHandlerActionDelayed(mHandler, delay, 2 * delay);
         verify(mMockCallbacks).imsServiceFeatureRemoved(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
                 eq(mTestImsServiceController));
         verify(mMockCallbacks).imsServiceFeatureRemoved(eq(SLOT_0), eq(ImsFeature.FEATURE_RCS),
@@ -660,6 +662,8 @@
 
         conn.onServiceDisconnected(mTestComponentName);
 
+        long delay = mTestImsServiceController.getRebindDelay();
+        waitForHandlerActionDelayed(mHandler, delay, 2 * delay);
         verify(mMockCallbacks).imsServiceFeatureRemoved(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
                 eq(mTestImsServiceController));
         verify(mMockCallbacks).imsServiceFeatureRemoved(eq(SLOT_0), eq(ImsFeature.FEATURE_RCS),
@@ -762,6 +766,8 @@
 
         conn.onBindingDied(null /*null*/);
 
+        long delay = REBIND_RETRY.getStartDelay();
+        waitForHandlerActionDelayed(mHandler, delay, 2 * delay);
         verify(mMockContext).unbindService(eq(conn));
         verify(mMockCallbacks).imsServiceFeatureRemoved(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
                 eq(mTestImsServiceController));
@@ -787,6 +793,8 @@
         slotIdToSubIdMap.put(SLOT_0, SUB_2);
         bindAndNullServiceError(testFeatures, slotIdToSubIdMap.clone());
 
+        long delay = mTestImsServiceController.getRebindDelay();
+        waitForHandlerActionDelayed(mHandler, delay, 2 * delay);
         verify(mMockCallbacks, never()).imsServiceFeatureCreated(anyInt(), anyInt(),
                 eq(mTestImsServiceController));
         verify(mMockCallbacks).imsServiceBindPermanentError(eq(mTestComponentName));
@@ -1304,7 +1312,7 @@
 
         conn.onBindingDied(null /*null*/);
 
-        long delay = mTestImsServiceController.getRebindDelay();
+        long delay = REBIND_RETRY.getStartDelay();
         waitForHandlerActionDelayed(mHandler, delay, 2 * delay);
         // The service should autobind after rebind event occurs
         verify(mMockContext, times(2)).bindService(any(), any(), anyInt());
@@ -1330,7 +1338,7 @@
         // null binding should be ignored in this case.
         conn.onNullBinding(null);
 
-        long delay = mTestImsServiceController.getRebindDelay();
+        long delay = REBIND_RETRY.getStartDelay();
         waitForHandlerActionDelayed(mHandler, delay, 2 * delay);
         // The service should autobind after rebind event occurs
         verify(mMockContext, times(2)).bindService(any(), any(), anyInt());
@@ -1398,10 +1406,11 @@
         slotIdToSubIdMap.put(SLOT_0, SUB_2);
         ServiceConnection conn = bindAndConnectService(testFeatures, slotIdToSubIdMap.clone());
         conn.onBindingDied(null /*null*/);
+
+        long delay = REBIND_RETRY.getStartDelay();
+        waitForHandlerActionDelayed(mHandler, delay, 2 * delay);
         mTestImsServiceController.bind(testFeatures, slotIdToSubIdMap.clone());
 
-        long delay = mTestImsServiceController.getRebindDelay();
-        waitForHandlerActionDelayed(mHandler, delay, 2 * delay);
         // Should only see two binds, not three from the auto rebind that occurs.
         verify(mMockContext, times(2)).bindService(any(), any(), anyInt());
     }
@@ -1481,6 +1490,9 @@
         IImsServiceController.Stub controllerStub = mock(IImsServiceController.Stub.class);
         when(controllerStub.queryLocalInterface(any())).thenReturn(mMockServiceControllerBinder);
         connection.onServiceConnected(mTestComponentName, controllerStub);
+
+        long delay = mTestImsServiceController.getRebindDelay();
+        waitForHandlerActionDelayed(mHandler, delay, 2 * delay);
         return connection;
     }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsTestBase.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsTestBase.java
index 63fcf10..c6b0fa1 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsTestBase.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsTestBase.java
@@ -21,6 +21,8 @@
 import android.content.Context;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
 import android.testing.TestableLooper;
 
 import androidx.test.InstrumentationRegistry;
@@ -29,6 +31,7 @@
 
 import org.mockito.MockitoAnnotations;
 
+import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
@@ -38,6 +41,22 @@
  * Helper class to load Mockito Resources into a test.
  */
 public class ImsTestBase {
+    private static final Field MESSAGE_QUEUE_FIELD;
+    private static final Field MESSAGE_WHEN_FIELD;
+    private static final Field MESSAGE_NEXT_FIELD;
+
+    static {
+        try {
+            MESSAGE_QUEUE_FIELD = MessageQueue.class.getDeclaredField("mMessages");
+            MESSAGE_QUEUE_FIELD.setAccessible(true);
+            MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when");
+            MESSAGE_WHEN_FIELD.setAccessible(true);
+            MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
+            MESSAGE_NEXT_FIELD.setAccessible(true);
+        } catch (NoSuchFieldException e) {
+            throw new RuntimeException("Failed to initialize TelephonyTest", e);
+        }
+    }
 
     protected Context mContext;
     protected List<TestableLooper> mTestableLoopers = new ArrayList<>();
@@ -112,6 +131,52 @@
     }
 
     /**
+     * @return The longest delay from all the message queues.
+     */
+    private long getLongestDelay() {
+        long delay = 0;
+        for (TestableLooper looper : mTestableLoopers) {
+            MessageQueue queue = looper.getLooper().getQueue();
+            try {
+                Message msg = (Message) MESSAGE_QUEUE_FIELD.get(queue);
+                while (msg != null) {
+                    delay = Math.max(msg.getWhen(), delay);
+                    msg = (Message) MESSAGE_NEXT_FIELD.get(msg);
+                }
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException("Access failed in TelephonyTest", e);
+            }
+        }
+        return delay;
+    }
+
+    /**
+     * @return {@code true} if there are any messages in the queue.
+     */
+    private boolean messagesExist() {
+        for (TestableLooper looper : mTestableLoopers) {
+            MessageQueue queue = looper.getLooper().getQueue();
+            try {
+                Message msg = (Message) MESSAGE_QUEUE_FIELD.get(queue);
+                if (msg != null) return true;
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException("Access failed in TelephonyTest", e);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Handle all messages including the delayed messages.
+     */
+    public void processAllFutureMessages() {
+        while (messagesExist()) {
+            moveTimeForward(getLongestDelay());
+            processAllMessages();
+        }
+    }
+
+    /**
      * Check if there are any messages to be processed in any monitored TestableLooper
      * Delayed messages to be handled at a later time will be ignored
      * @return true if there are no messages that can be handled at the current time
@@ -123,4 +188,28 @@
         }
         return true;
     }
+
+    /**
+     * Effectively moves time forward by reducing the time of all messages
+     * for all monitored TestableLoopers
+     * @param milliSeconds number of milliseconds to move time forward by
+     */
+    public void moveTimeForward(long milliSeconds) {
+        for (TestableLooper looper : mTestableLoopers) {
+            MessageQueue queue = looper.getLooper().getQueue();
+            try {
+                Message msg = (Message) MESSAGE_QUEUE_FIELD.get(queue);
+                while (msg != null) {
+                    long updatedWhen = msg.getWhen() - milliSeconds;
+                    if (updatedWhen < 0) {
+                        updatedWhen = 0;
+                    }
+                    MESSAGE_WHEN_FIELD.set(msg, updatedWhen);
+                    msg = (Message) MESSAGE_NEXT_FIELD.get(msg);
+                }
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException("Access failed in TelephonyTest", e);
+            }
+        }
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsCallInfoTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsCallInfoTrackerTest.java
new file mode 100644
index 0000000..e3fc6d3
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsCallInfoTrackerTest.java
@@ -0,0 +1,436 @@
+/*
+ * Copyright (C) 2022 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.imsphone;
+
+import static android.telephony.AccessNetworkConstants.AccessNetworkType.EUTRAN;
+
+import static junit.framework.Assert.assertNotNull;
+
+import static org.junit.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.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.telephony.ServiceState;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.telephony.Call;
+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.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class ImsCallInfoTrackerTest extends TelephonyTest {
+
+    private ImsCallInfoTracker mImsCallInfoTracker;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+
+        mImsCallInfoTracker = new ImsCallInfoTracker(mImsPhone);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    public void testDialingNormalCall() throws Exception {
+        ArgumentCaptor<List<ImsCallInfo>> captor = ArgumentCaptor.forClass(List.class);
+
+        ImsPhoneConnection c = getConnection(Call.State.DIALING, false);
+        mImsCallInfoTracker.addImsCallStatus(c);
+
+        verify(mImsPhone, times(1)).updateImsCallStatus(captor.capture(), any());
+
+        List<ImsCallInfo> imsCallInfos = captor.getValue();
+
+        assertNotNull(imsCallInfos);
+        assertEquals(1, imsCallInfos.size());
+
+        ImsCallInfo info = imsCallInfos.get(0);
+
+        assertNotNull(info);
+        assertEquals(1, info.getIndex());
+        assertEquals(Call.State.DIALING, info.getCallState());
+        assertFalse(info.isIncoming());
+        assertFalse(info.isEmergencyCall());
+        assertEquals(EUTRAN, info.getCallRadioTech());
+        assertFalse(info.isHeldByRemote());
+    }
+
+    @Test
+    public void testDialingEmergencyCall() throws Exception {
+        ArgumentCaptor<List<ImsCallInfo>> captor = ArgumentCaptor.forClass(List.class);
+
+        ImsPhoneConnection c = getConnection(Call.State.DIALING, true);
+        mImsCallInfoTracker.addImsCallStatus(c);
+
+        verify(mImsPhone, times(1)).updateImsCallStatus(captor.capture(), any());
+
+        List<ImsCallInfo> imsCallInfos = captor.getValue();
+
+        assertNotNull(imsCallInfos);
+        assertEquals(1, imsCallInfos.size());
+
+        ImsCallInfo info = imsCallInfos.get(0);
+
+        assertNotNull(info);
+        assertEquals(1, info.getIndex());
+        assertEquals(Call.State.DIALING, info.getCallState());
+        assertFalse(info.isIncoming());
+        assertTrue(info.isEmergencyCall());
+        assertEquals(EUTRAN, info.getCallRadioTech());
+        assertFalse(info.isHeldByRemote());
+    }
+
+    @Test
+    public void testIncomingCall() throws Exception {
+        ArgumentCaptor<List<ImsCallInfo>> captor = ArgumentCaptor.forClass(List.class);
+
+        ImsPhoneConnection c = getConnection(Call.State.INCOMING, false);
+        mImsCallInfoTracker.addImsCallStatus(c);
+
+        verify(mImsPhone, times(1)).updateImsCallStatus(captor.capture(), any());
+
+        List<ImsCallInfo> imsCallInfos = captor.getValue();
+
+        assertNotNull(imsCallInfos);
+        assertEquals(1, imsCallInfos.size());
+
+        ImsCallInfo info = imsCallInfos.get(0);
+
+        assertNotNull(info);
+        assertEquals(1, info.getIndex());
+        assertEquals(Call.State.INCOMING, info.getCallState());
+        assertTrue(info.isIncoming());
+        assertFalse(info.isEmergencyCall());
+        assertEquals(EUTRAN, info.getCallRadioTech());
+        assertFalse(info.isHeldByRemote());
+
+        // Answer the call
+        doReturn(Call.State.ACTIVE).when(c).getState();
+        mImsCallInfoTracker.updateImsCallStatus(c);
+
+        verify(mImsPhone, times(2)).updateImsCallStatus(captor.capture(), any());
+
+        imsCallInfos = captor.getValue();
+
+        assertNotNull(imsCallInfos);
+        assertEquals(1, imsCallInfos.size());
+
+        info = imsCallInfos.get(0);
+
+        assertNotNull(info);
+        assertEquals(1, info.getIndex());
+        assertEquals(Call.State.ACTIVE, info.getCallState());
+
+        // Hold the call
+        doReturn(Call.State.HOLDING).when(c).getState();
+        mImsCallInfoTracker.updateImsCallStatus(c);
+
+        verify(mImsPhone, times(3)).updateImsCallStatus(captor.capture(), any());
+
+        imsCallInfos = captor.getValue();
+
+        assertNotNull(imsCallInfos);
+        assertEquals(1, imsCallInfos.size());
+
+        info = imsCallInfos.get(0);
+
+        assertNotNull(info);
+        assertEquals(1, info.getIndex());
+        assertEquals(Call.State.HOLDING, info.getCallState());
+
+        // Disconnect the call
+        doReturn(Call.State.DISCONNECTING).when(c).getState();
+        mImsCallInfoTracker.updateImsCallStatus(c);
+
+        verify(mImsPhone, times(4)).updateImsCallStatus(captor.capture(), any());
+
+        imsCallInfos = captor.getValue();
+
+        assertNotNull(imsCallInfos);
+        assertEquals(1, imsCallInfos.size());
+
+        info = imsCallInfos.get(0);
+
+        assertNotNull(info);
+        assertEquals(1, info.getIndex());
+        assertEquals(Call.State.DISCONNECTING, info.getCallState());
+
+        // Call disconnected
+        doReturn(Call.State.DISCONNECTED).when(c).getState();
+        mImsCallInfoTracker.updateImsCallStatus(c);
+
+        verify(mImsPhone, times(5)).updateImsCallStatus(captor.capture(), any());
+
+        imsCallInfos = captor.getValue();
+
+        assertNotNull(imsCallInfos);
+        assertEquals(1, imsCallInfos.size());
+
+        info = imsCallInfos.get(0);
+
+        assertNotNull(info);
+        assertEquals(1, info.getIndex());
+        assertEquals(Call.State.IDLE, info.getCallState());
+    }
+
+    @Test
+    public void testMultiCalls() throws Exception {
+        ArgumentCaptor<List<ImsCallInfo>> captor = ArgumentCaptor.forClass(List.class);
+
+        ImsPhoneConnection c1 = getConnection(Call.State.INCOMING, false);
+        mImsCallInfoTracker.addImsCallStatus(c1);
+
+        verify(mImsPhone, times(1)).updateImsCallStatus(captor.capture(), any());
+
+        doReturn(Call.State.ACTIVE).when(c1).getState();
+        mImsCallInfoTracker.updateImsCallStatus(c1);
+
+        verify(mImsPhone, times(2)).updateImsCallStatus(captor.capture(), any());
+
+        List<ImsCallInfo> imsCallInfos = captor.getValue();
+
+        assertNotNull(imsCallInfos);
+        assertEquals(1, imsCallInfos.size());
+
+        // 1st call
+        ImsCallInfo info1 = imsCallInfos.get(0);
+
+        assertNotNull(info1);
+        assertEquals(1, info1.getIndex());
+        assertEquals(Call.State.ACTIVE, info1.getCallState());
+
+        // Add 2nd WAITING call
+        ImsPhoneConnection c2 = getConnection(Call.State.WAITING, false);
+        mImsCallInfoTracker.addImsCallStatus(c2);
+
+        verify(mImsPhone, times(3)).updateImsCallStatus(captor.capture(), any());
+
+        imsCallInfos = captor.getValue();
+
+        assertNotNull(imsCallInfos);
+        assertEquals(2, imsCallInfos.size());
+
+        // 1st call
+        info1 = imsCallInfos.get(0);
+
+        assertNotNull(info1);
+        assertEquals(1, info1.getIndex());
+        assertEquals(Call.State.ACTIVE, info1.getCallState());
+
+        // 2nd call
+        ImsCallInfo info2 = imsCallInfos.get(1);
+
+        assertNotNull(info2);
+        assertEquals(2, info2.getIndex());
+        assertEquals(Call.State.WAITING, info2.getCallState());
+        assertTrue(info2.isIncoming());
+
+        // Disconnect 1st call
+        doReturn(Call.State.DISCONNECTED).when(c1).getState();
+        mImsCallInfoTracker.updateImsCallStatus(c1);
+
+        verify(mImsPhone, times(4)).updateImsCallStatus(captor.capture(), any());
+
+        imsCallInfos = captor.getValue();
+
+        assertNotNull(imsCallInfos);
+        assertEquals(2, imsCallInfos.size());
+
+        // 1st call
+        info1 = imsCallInfos.get(0);
+
+        assertNotNull(info1);
+        assertEquals(1, info1.getIndex());
+        assertEquals(Call.State.IDLE, info1.getCallState());
+
+        // 2nd call
+        info2 = imsCallInfos.get(1);
+
+        assertNotNull(info2);
+        assertEquals(2, info2.getIndex());
+        assertEquals(Call.State.WAITING, info2.getCallState());
+
+        // Answer WAITING call
+        doReturn(Call.State.ACTIVE).when(c2).getState();
+        mImsCallInfoTracker.updateImsCallStatus(c2);
+
+        verify(mImsPhone, times(5)).updateImsCallStatus(captor.capture(), any());
+
+        imsCallInfos = captor.getValue();
+
+        assertNotNull(imsCallInfos);
+        assertEquals(1, imsCallInfos.size());
+
+        // 2nd call
+        info2 = imsCallInfos.get(0);
+
+        assertNotNull(info2);
+        assertEquals(2, info2.getIndex());
+        assertEquals(Call.State.ACTIVE, info2.getCallState());
+    }
+
+    @Test
+    public void testHeldByRemote() throws Exception {
+        ArgumentCaptor<List<ImsCallInfo>> captor = ArgumentCaptor.forClass(List.class);
+
+        ImsPhoneConnection c = getConnection(Call.State.INCOMING, false);
+        mImsCallInfoTracker.addImsCallStatus(c);
+
+        verify(mImsPhone, times(1)).updateImsCallStatus(captor.capture(), any());
+
+        doReturn(Call.State.ACTIVE).when(c).getState();
+        mImsCallInfoTracker.updateImsCallStatus(c);
+
+        verify(mImsPhone, times(2)).updateImsCallStatus(captor.capture(), any());
+
+        // Hold received
+        mImsCallInfoTracker.updateImsCallStatus(c, true, false);
+
+        verify(mImsPhone, times(3)).updateImsCallStatus(captor.capture(), any());
+
+        List<ImsCallInfo> imsCallInfos = captor.getValue();
+
+        assertEquals(1, imsCallInfos.size());
+
+        ImsCallInfo info = imsCallInfos.get(0);
+
+        assertNotNull(info);
+        assertEquals(1, info.getIndex());
+        assertEquals(Call.State.ACTIVE, info.getCallState());
+        assertTrue(info.isHeldByRemote());
+
+        // Resume received
+        mImsCallInfoTracker.updateImsCallStatus(c, false, true);
+
+        verify(mImsPhone, times(4)).updateImsCallStatus(captor.capture(), any());
+
+        imsCallInfos = captor.getValue();
+
+        assertEquals(1, imsCallInfos.size());
+
+        info = imsCallInfos.get(0);
+
+        assertNotNull(info);
+        assertEquals(1, info.getIndex());
+        assertEquals(Call.State.ACTIVE, info.getCallState());
+        assertFalse(info.isHeldByRemote());
+    }
+
+    @Test
+    public void testSortImsCallInfo() throws Exception {
+        List<ImsCallInfo> imsCallInfos = new ArrayList<>();
+        imsCallInfos.add(new ImsCallInfo(2));
+        imsCallInfos.add(new ImsCallInfo(1));
+
+        assertEquals(2, imsCallInfos.get(0).getIndex());
+        assertEquals(1, imsCallInfos.get(1).getIndex());
+
+        ImsCallInfoTracker.sort(imsCallInfos);
+
+        assertEquals(1, imsCallInfos.get(0).getIndex());
+        assertEquals(2, imsCallInfos.get(1).getIndex());
+    }
+
+    @Test
+    public void testSrvccCompleted() throws Exception {
+        ArgumentCaptor<List<ImsCallInfo>> captor = ArgumentCaptor.forClass(List.class);
+
+        ImsPhoneConnection c = getConnection(Call.State.DIALING, false);
+        mImsCallInfoTracker.addImsCallStatus(c);
+
+        verify(mImsPhone, times(1)).updateImsCallStatus(captor.capture(), any());
+
+        List<ImsCallInfo> imsCallInfos = captor.getValue();
+
+        assertNotNull(imsCallInfos);
+        assertEquals(1, imsCallInfos.size());
+
+        mImsCallInfoTracker.notifySrvccCompleted();
+
+        verify(mImsPhone, times(2)).updateImsCallStatus(captor.capture(), any());
+
+        imsCallInfos = captor.getValue();
+
+        assertNotNull(imsCallInfos);
+        assertEquals(0, imsCallInfos.size());
+    }
+
+    @Test
+    public void testClearAllOrphanedConnections() throws Exception {
+        ArgumentCaptor<List<ImsCallInfo>> captor = ArgumentCaptor.forClass(List.class);
+
+        ImsPhoneConnection c = getConnection(Call.State.DIALING, false);
+        mImsCallInfoTracker.addImsCallStatus(c);
+
+        verify(mImsPhone, times(1)).updateImsCallStatus(captor.capture(), any());
+
+        List<ImsCallInfo> imsCallInfos = captor.getValue();
+
+        assertNotNull(imsCallInfos);
+        assertEquals(1, imsCallInfos.size());
+
+        mImsCallInfoTracker.clearAllOrphanedConnections();
+
+        verify(mImsPhone, times(2)).updateImsCallStatus(captor.capture(), any());
+
+        imsCallInfos = captor.getValue();
+
+        assertNotNull(imsCallInfos);
+        assertEquals(1, imsCallInfos.size());
+
+        ImsCallInfo info = imsCallInfos.get(0);
+
+        assertNotNull(info);
+        assertEquals(1, info.getIndex());
+        assertEquals(Call.State.IDLE, info.getCallState());
+    }
+
+    private ImsPhoneConnection getConnection(Call.State state, boolean isEmergency) {
+        ImsPhoneConnection c = mock(ImsPhoneConnection.class);
+        doReturn(isEmergency).when(c).isEmergencyCall();
+        doReturn(state).when(c).getState();
+        doReturn(ServiceState.RIL_RADIO_TECHNOLOGY_LTE).when(c).getCallRadioTech();
+        switch (state) {
+            case INCOMING:
+            case WAITING:
+                doReturn(true).when(c).isIncoming();
+                break;
+            default:
+                doReturn(false).when(c).isIncoming();
+        }
+
+        return c;
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsNrSaModeHandlerTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsNrSaModeHandlerTest.java
new file mode 100644
index 0000000..7d6557d
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsNrSaModeHandlerTest.java
@@ -0,0 +1,384 @@
+/*
+ * 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.imsphone;
+
+import static android.telephony.CarrierConfigManager.CARRIER_NR_AVAILABILITY_NSA;
+import static android.telephony.CarrierConfigManager.CARRIER_NR_AVAILABILITY_SA;
+import static android.telephony.CarrierConfigManager.Ims.KEY_NR_SA_DISABLE_POLICY_INT;
+import static android.telephony.CarrierConfigManager.Ims.NR_SA_DISABLE_POLICY_NONE;
+import static android.telephony.CarrierConfigManager.Ims.NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED;
+import static android.telephony.CarrierConfigManager.Ims.NR_SA_DISABLE_POLICY_WFC_ESTABLISHED;
+import static android.telephony.CarrierConfigManager.Ims.NR_SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED;
+import static android.telephony.CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY;
+import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN;
+import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NONE;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.os.Handler;
+import android.telephony.CarrierConfigManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.ArraySet;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.Set;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public final class ImsNrSaModeHandlerTest extends TelephonyTest{
+    @Captor ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener>
+            mCarrierConfigChangeListenerCaptor;
+    @Captor ArgumentCaptor<Handler> mPreciseCallStateHandlerCaptor;
+
+    private ImsNrSaModeHandler mTestImsNrSaModeHandler;
+    private CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener;
+    private Handler mPreciseCallStateHandler;
+
+    @Mock private ImsPhoneCall mForegroundCall;
+    @Mock private ImsPhoneCall mBackgroundCall;
+    private Call.State mActiveState = ImsPhoneCall.State.ACTIVE;
+    private Call.State mIdleState = ImsPhoneCall.State.IDLE;
+
+    private int mAnyInt = 0;
+    private final Set<String> mFeatureTags =
+            new ArraySet<String>(Arrays.asList(ImsNrSaModeHandler.MMTEL_FEATURE_TAG));
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        MockitoAnnotations.initMocks(this);
+
+        mTestImsNrSaModeHandler = new ImsNrSaModeHandler(mImsPhone, mTestableLooper.getLooper());
+
+        verify(mCarrierConfigManager).registerCarrierConfigChangeListener(
+                any(), mCarrierConfigChangeListenerCaptor.capture());
+
+        mCarrierConfigChangeListener = mCarrierConfigChangeListenerCaptor.getValue();
+
+        doReturn(mAnyInt).when(mImsPhone).getSubId();
+        doReturn(mContextFixture.getCarrierConfigBundle()).when(mCarrierConfigManager)
+                .getConfigForSubId(anyInt(), any());
+        doReturn(mPhone).when(mImsPhone).getDefaultPhone();
+
+        doReturn(mForegroundCall).when(mImsPhone).getForegroundCall();
+        doReturn(mBackgroundCall).when(mImsPhone).getBackgroundCall();
+
+        doReturn(mActiveState).when(mForegroundCall).getState();
+        doReturn(mActiveState).when(mBackgroundCall).getState();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mTestImsNrSaModeHandler = null;
+        super.tearDown();
+    }
+
+    @Test
+    public void testTearDown() {
+        mContextFixture.getCarrierConfigBundle().putInt(
+                KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_WFC_ESTABLISHED);
+        mContextFixture.getCarrierConfigBundle().putIntArray(
+                KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA});
+
+        mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt);
+
+        verify(mImsPhone).registerForPreciseCallStateChanged(
+                mPreciseCallStateHandlerCaptor.capture(), anyInt(), any());
+        mPreciseCallStateHandler = mPreciseCallStateHandlerCaptor.getValue();
+
+        mSimulatedCommands.setN1ModeEnabled(false, null);
+        mTestImsNrSaModeHandler.setNrSaDisabledForWfc(true);
+
+        mTestImsNrSaModeHandler.tearDown();
+
+        verify(mCarrierConfigManager).unregisterCarrierConfigChangeListener(any());
+        verify(mImsPhone).unregisterForPreciseCallStateChanged(mPreciseCallStateHandler);
+        assertTrue(mSimulatedCommands.isN1ModeEnabled());
+    }
+
+    @Test
+    public void testOnImsRegisteredWithSaDisablePolicyNone() {
+        mContextFixture.getCarrierConfigBundle().putInt(
+                KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_NONE);
+        mContextFixture.getCarrierConfigBundle().putIntArray(
+                KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA});
+
+        mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt);
+
+        mTestImsNrSaModeHandler.setVowifiRegStatus(false);
+
+        mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_IWLAN, mFeatureTags);
+
+        assertFalse(mTestImsNrSaModeHandler.isVowifiRegistered());
+    }
+
+    @Test
+    public void testOnImsRegisteredWithSaDisablePolicyWfcEstablished() {
+        mContextFixture.getCarrierConfigBundle().putInt(
+                KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_WFC_ESTABLISHED);
+        mContextFixture.getCarrierConfigBundle().putIntArray(
+                KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA});
+
+        mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt);
+
+        verify(mImsPhone).registerForPreciseCallStateChanged(any(), anyInt(), any());
+
+        mSimulatedCommands.setN1ModeEnabled(false, null);
+        mTestImsNrSaModeHandler.setVowifiRegStatus(true);
+        mTestImsNrSaModeHandler.setImsCallStatus(true);
+
+        mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_NONE, mFeatureTags);
+
+        assertFalse(mTestImsNrSaModeHandler.isVowifiRegistered());
+        assertTrue(mSimulatedCommands.isN1ModeEnabled());
+    }
+
+    @Test
+    public void testOnImsRegisteredWithSaDisablePolicyWfcEstablishedWithVonrDisabled() {
+        mContextFixture.getCarrierConfigBundle().putInt(
+                KEY_NR_SA_DISABLE_POLICY_INT,
+                NR_SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED);
+        mContextFixture.getCarrierConfigBundle().putIntArray(
+                KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA});
+
+        mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt);
+
+        verify(mImsPhone).registerForPreciseCallStateChanged(any(), anyInt(), any());
+
+        mSimulatedCommands.setN1ModeEnabled(true, null);
+        mTestImsNrSaModeHandler.setVowifiRegStatus(false);
+        mTestImsNrSaModeHandler.setImsCallStatus(true);
+        mSimulatedCommands.setVonrEnabled(true);
+
+        mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_IWLAN, mFeatureTags);
+        processAllMessages();
+
+        assertTrue(mTestImsNrSaModeHandler.isVowifiRegistered());
+        assertTrue(mSimulatedCommands.isN1ModeEnabled());
+
+        mSimulatedCommands.setN1ModeEnabled(true, null);
+        mTestImsNrSaModeHandler.setVowifiRegStatus(false);
+        mTestImsNrSaModeHandler.setImsCallStatus(true);
+        mSimulatedCommands.setVonrEnabled(false);
+
+        mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_IWLAN, mFeatureTags);
+        processAllMessages();
+
+        assertTrue(mTestImsNrSaModeHandler.isVowifiRegistered());
+        assertFalse(mSimulatedCommands.isN1ModeEnabled());
+
+        mSimulatedCommands.setN1ModeEnabled(true, null);
+        mTestImsNrSaModeHandler.setVowifiRegStatus(false);
+        mTestImsNrSaModeHandler.setImsCallStatus(true);
+        mSimulatedCommands.setVonrEnabled(false);
+
+        mFeatureTags.remove(ImsNrSaModeHandler.MMTEL_FEATURE_TAG);
+        mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_IWLAN, mFeatureTags);
+        processAllMessages();
+
+        assertFalse(mTestImsNrSaModeHandler.isVowifiRegistered());
+        assertTrue(mSimulatedCommands.isN1ModeEnabled());
+
+        mSimulatedCommands.setN1ModeEnabled(true, null);
+        mTestImsNrSaModeHandler.setVowifiRegStatus(false);
+        mTestImsNrSaModeHandler.setImsCallStatus(true);
+        mSimulatedCommands.setVonrEnabled(false);
+
+        mFeatureTags.add(ImsNrSaModeHandler.MMTEL_FEATURE_TAG);
+        mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_IWLAN, mFeatureTags);
+        processAllMessages();
+
+        assertTrue(mTestImsNrSaModeHandler.isVowifiRegistered());
+        assertFalse(mSimulatedCommands.isN1ModeEnabled());
+    }
+
+    @Test
+    public void testOnImsRegisteredWithSaDisablePolicyVowifiRegistered() {
+        mContextFixture.getCarrierConfigBundle().putInt(
+                KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED);
+        mContextFixture.getCarrierConfigBundle().putIntArray(
+                KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA});
+
+        mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt);
+
+        mSimulatedCommands.setN1ModeEnabled(true, null);
+        mTestImsNrSaModeHandler.setVowifiRegStatus(false);
+
+        mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_IWLAN, mFeatureTags);
+
+        assertTrue(mTestImsNrSaModeHandler.isVowifiRegistered());
+        assertFalse(mSimulatedCommands.isN1ModeEnabled());
+
+        mSimulatedCommands.setN1ModeEnabled(false, null);
+        mTestImsNrSaModeHandler.setVowifiRegStatus(true);
+
+        mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_NONE, mFeatureTags);
+
+        assertFalse(mTestImsNrSaModeHandler.isVowifiRegistered());
+        assertTrue(mSimulatedCommands.isN1ModeEnabled());
+    }
+
+    @Test
+    public void testOnImsUnregisteredDoNothingIfNotVowifiRegNoti() {
+        mContextFixture.getCarrierConfigBundle().putInt(
+                KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED);
+        mContextFixture.getCarrierConfigBundle().putIntArray(
+                KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA});
+
+        mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt);
+
+        mTestImsNrSaModeHandler.setVowifiRegStatus(true);
+
+        mTestImsNrSaModeHandler.onImsUnregistered(REGISTRATION_TECH_NONE);
+
+        assertTrue(mTestImsNrSaModeHandler.isVowifiRegistered());
+    }
+
+    @Test
+    public void testOnImsUnregisteredWithSaDisablePolicyVowifiRegistered() {
+        mContextFixture.getCarrierConfigBundle().putInt(
+                KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED);
+        mContextFixture.getCarrierConfigBundle().putIntArray(
+                KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA});
+
+        mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt);
+
+        mSimulatedCommands.setN1ModeEnabled(false, null);
+        mTestImsNrSaModeHandler.setVowifiRegStatus(true);
+
+        mTestImsNrSaModeHandler.onImsUnregistered(REGISTRATION_TECH_IWLAN);
+
+        assertFalse(mTestImsNrSaModeHandler.isVowifiRegistered());
+        assertTrue(mSimulatedCommands.isN1ModeEnabled());
+
+        mSimulatedCommands.setN1ModeEnabled(false, null);
+        mTestImsNrSaModeHandler.setVowifiRegStatus(true);
+
+        mTestImsNrSaModeHandler.onImsUnregistered(REGISTRATION_TECH_NONE);
+
+        assertTrue(mTestImsNrSaModeHandler.isVowifiRegistered());
+        assertFalse(mSimulatedCommands.isN1ModeEnabled());
+    }
+
+    @Test
+    public void testOnPreciseCallStateChangedWithSaDisablePolicyWfcEstablished() {
+        mContextFixture.getCarrierConfigBundle().putInt(
+                KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_WFC_ESTABLISHED);
+        mContextFixture.getCarrierConfigBundle().putIntArray(
+                KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA});
+
+        mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt);
+
+        verify(mImsPhone).registerForPreciseCallStateChanged(
+                mPreciseCallStateHandlerCaptor.capture(), anyInt(), any());
+        mPreciseCallStateHandler = mPreciseCallStateHandlerCaptor.getValue();
+
+        mTestImsNrSaModeHandler.setVowifiRegStatus(true);
+        mSimulatedCommands.setN1ModeEnabled(true, null);
+
+        mPreciseCallStateHandler.handleMessage(mPreciseCallStateHandler.obtainMessage(101));
+
+        assertTrue(mTestImsNrSaModeHandler.isImsCallOngoing());
+        assertFalse(mSimulatedCommands.isN1ModeEnabled());
+
+        mTestImsNrSaModeHandler.setVowifiRegStatus(false);
+        mSimulatedCommands.setN1ModeEnabled(true, null);
+
+        doReturn(mActiveState).when(mForegroundCall).getState();
+        doReturn(mActiveState).when(mBackgroundCall).getState();
+        mPreciseCallStateHandler.handleMessage(mPreciseCallStateHandler.obtainMessage(101));
+
+        assertTrue(mTestImsNrSaModeHandler.isImsCallOngoing());
+        assertTrue(mSimulatedCommands.isN1ModeEnabled());
+
+        mTestImsNrSaModeHandler.setVowifiRegStatus(false);
+        mTestImsNrSaModeHandler.setImsCallStatus(false);
+        mSimulatedCommands.setN1ModeEnabled(true, null);
+
+        doReturn(mIdleState).when(mForegroundCall).getState();
+        doReturn(mIdleState).when(mBackgroundCall).getState();
+        mPreciseCallStateHandler.handleMessage(mPreciseCallStateHandler.obtainMessage(101));
+
+        assertFalse(mTestImsNrSaModeHandler.isImsCallOngoing());
+        assertTrue(mSimulatedCommands.isN1ModeEnabled());
+
+        mTestImsNrSaModeHandler.setVowifiRegStatus(true);
+        mTestImsNrSaModeHandler.setImsCallStatus(true);
+        mSimulatedCommands.setN1ModeEnabled(false, null);
+        mPreciseCallStateHandler.handleMessage(mPreciseCallStateHandler.obtainMessage(101));
+
+        assertFalse(mTestImsNrSaModeHandler.isImsCallOngoing());
+        assertTrue(mSimulatedCommands.isN1ModeEnabled());
+    }
+
+    @Test
+    public void testUnregisterForPreciseCallStateChangeIfNeeded() {
+        mContextFixture.getCarrierConfigBundle().putInt(
+                KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_WFC_ESTABLISHED);
+        mContextFixture.getCarrierConfigBundle().putIntArray(
+                KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA});
+
+        mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt);
+
+        verify(mImsPhone).registerForPreciseCallStateChanged(
+                mPreciseCallStateHandlerCaptor.capture(), anyInt(), any());
+        mPreciseCallStateHandler = mPreciseCallStateHandlerCaptor.getValue();
+
+        mContextFixture.getCarrierConfigBundle().putInt(
+                KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED);
+
+        mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt);
+
+        verify(mImsPhone).unregisterForPreciseCallStateChanged(mPreciseCallStateHandler);
+    }
+
+    @Test
+    public void testNrSaModeIsNotHandledWhenNotSupported() {
+        mContextFixture.getCarrierConfigBundle().putInt(
+                KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_WFC_ESTABLISHED);
+        mContextFixture.getCarrierConfigBundle().putIntArray(
+                KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_NSA});
+
+        mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt);
+
+        mSimulatedCommands.setN1ModeEnabled(false, null);
+        mTestImsNrSaModeHandler.setVowifiRegStatus(true);
+
+        mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_NONE, mFeatureTags);
+
+        assertFalse(mTestImsNrSaModeHandler.isVowifiRegistered());
+        assertFalse(mSimulatedCommands.isN1ModeEnabled());
+    }
+}
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 c2db93f..c4bb864 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
@@ -27,6 +28,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.telephony.ims.ImsCallProfile;
+import android.telephony.ims.ImsCallSession;
 import android.telephony.ims.ImsStreamMediaProfile;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -258,6 +261,51 @@
 
     @Test
     @SmallTest
+    public void testGetCallSessionId() {
+        doReturn(mImsCall).when(mConnection1).getImsCall();
+        ImsCallSession imsForegroundCallSession = mock(ImsCallSession.class);
+        doReturn(imsForegroundCallSession).when(mImsCall).getSession();
+        doReturn("1").when(imsForegroundCallSession).getCallId();
+        mImsCallUT.attach(mConnection1, Call.State.ACTIVE);
+        assertEquals("1", mImsCallUT.getCallSessionId());
+        doReturn(null).when(mImsCall).getSession();
+        assertNull(mImsCallUT.getCallSessionId());
+        doReturn(null).when(mConnection1).getImsCall();
+        assertNull(mImsCallUT.getCallSessionId());
+        mImsCallUT.detach(mConnection1);
+        assertNull(mImsCallUT.getCallSessionId());
+    }
+
+    @Test
+    @SmallTest
+    public void testGetServiceType() {
+        doReturn(mImsCall).when(mConnection1).getImsCall();
+        mImsCallUT.attach(mConnection1, Call.State.ACTIVE);
+        doReturn(false).when(mConnection1).isEmergencyCall();
+        assertEquals(ImsCallProfile.SERVICE_TYPE_NORMAL, mImsCallUT.getServiceType());
+        doReturn(true).when(mConnection1).isEmergencyCall();
+        assertEquals(ImsCallProfile.SERVICE_TYPE_EMERGENCY, mImsCallUT.getServiceType());
+        mImsCallUT.detach(mConnection1);
+        assertEquals(ImsCallProfile.SERVICE_TYPE_NONE, mImsCallUT.getServiceType());
+    }
+
+    @Test
+    @SmallTest
+    public void testGetCallType() {
+        doReturn(mImsCall).when(mConnection1).getImsCall();
+        mImsCallUT.attach(mConnection1, Call.State.ACTIVE);
+        doReturn(false).when(mImsCall).isVideoCall();
+        assertEquals(ImsCallProfile.CALL_TYPE_VOICE, mImsCallUT.getCallType());
+        doReturn(true).when(mImsCall).isVideoCall();
+        assertEquals(ImsCallProfile.CALL_TYPE_VT, mImsCallUT.getCallType());
+        doReturn(null).when(mConnection1).getImsCall();
+        assertEquals(ImsCallProfile.CALL_TYPE_NONE, mImsCallUT.getCallType());
+        mImsCallUT.detach(mConnection1);
+        assertEquals(ImsCallProfile.CALL_TYPE_NONE, mImsCallUT.getCallType());
+    }
+
+    @Test
+    @SmallTest
     public void testSetMute() {
         doReturn(mImsCall).when(mConnection1).getImsCall();
         mImsCallUT.attach(mConnection1, Call.State.ACTIVE);
@@ -269,4 +317,24 @@
             fail("Exception unexpected");
         }
     }
+
+    @Test
+    public void testMaybeClearRemotelyHeldStatus() {
+        mImsCallUT.attach(mConnection1, Call.State.ACTIVE);
+        when(mConnection1.isHeldByRemote()).thenReturn(true);
+        mImsCallUT.maybeClearRemotelyHeldStatus();
+        verify(mConnection1, times(1)).setRemotelyUnheld();
+
+        mImsCallUT.attach(mConnection2, Call.State.ACTIVE);
+        when(mConnection2.isHeldByRemote()).thenReturn(true);
+        mImsCallUT.maybeClearRemotelyHeldStatus();
+        verify(mConnection1, times(2)).setRemotelyUnheld();
+        verify(mConnection2, times(1)).setRemotelyUnheld();
+
+        when(mConnection1.isHeldByRemote()).thenReturn(false);
+        when(mConnection2.isHeldByRemote()).thenReturn(false);
+        mImsCallUT.maybeClearRemotelyHeldStatus();
+        verify(mConnection1, times(2)).setRemotelyUnheld();
+        verify(mConnection2, times(1)).setRemotelyUnheld();
+    }
 }
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 906709a..d0a2094 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
@@ -21,6 +21,23 @@
 import static android.net.NetworkStats.SET_FOREGROUND;
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
+import static android.telephony.CarrierConfigManager.ImsVoice.ALERTING_SRVCC_SUPPORT;
+import static android.telephony.CarrierConfigManager.ImsVoice.BASIC_SRVCC_SUPPORT;
+import static android.telephony.CarrierConfigManager.ImsVoice.MIDCALL_SRVCC_SUPPORT;
+import static android.telephony.CarrierConfigManager.ImsVoice.PREALERTING_SRVCC_SUPPORT;
+import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_ACTIVE;
+import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_ALERTING;
+import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_INCOMING;
+import static android.telephony.PreciseCallState.PRECISE_CALL_STATE_INCOMING_SETUP;
+import static android.telephony.TelephonyManager.SRVCC_STATE_HANDOVER_CANCELED;
+import static android.telephony.TelephonyManager.SRVCC_STATE_HANDOVER_COMPLETED;
+import static android.telephony.TelephonyManager.SRVCC_STATE_HANDOVER_FAILED;
+import static android.telephony.TelephonyManager.SRVCC_STATE_HANDOVER_STARTED;
+import static android.telephony.emergency.EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_AMBULANCE;
+import static android.telephony.ims.ImsStreamMediaProfile.DIRECTION_INACTIVE;
+import static android.telephony.ims.ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
+import static android.telephony.ims.feature.MmTelFeature.IMS_TRAFFIC_DIRECTION_INCOMING;
+import static android.telephony.ims.feature.MmTelFeature.IMS_TRAFFIC_DIRECTION_OUTGOING;
 
 import static com.android.testutils.NetworkStatsUtilsKt.assertNetworkStatsEquals;
 
@@ -57,17 +74,20 @@
 import android.content.pm.PackageManager;
 import android.net.NetworkStats;
 import android.net.NetworkStats.Entry;
+import android.net.Uri;
 import android.net.netstats.provider.INetworkStatsProviderCallback;
 import android.os.Bundle;
 import android.os.Message;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.telecom.VideoProfile;
+import android.telephony.AccessNetworkConstants;
 import android.telephony.CarrierConfigManager;
 import android.telephony.DisconnectCause;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
+import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsCallProfile;
 import android.telephony.ims.ImsCallSession;
 import android.telephony.ims.ImsConferenceState;
@@ -75,6 +95,9 @@
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ImsStreamMediaProfile;
 import android.telephony.ims.RtpHeaderExtensionType;
+import android.telephony.ims.SrvccCall;
+import android.telephony.ims.aidl.IImsTrafficSessionCallback;
+import android.telephony.ims.aidl.ISrvccStartedCallback;
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
@@ -90,15 +113,19 @@
 import com.android.ims.ImsConfig;
 import com.android.ims.ImsException;
 import com.android.ims.ImsManager;
+import com.android.ims.internal.ConferenceParticipant;
 import com.android.ims.internal.IImsCallSession;
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.CallStateException;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.Connection;
 import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.SrvccConnection;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.d2d.RtpTransport;
+import com.android.internal.telephony.domainselection.DomainSelectionResolver;
 import com.android.internal.telephony.imsphone.ImsPhoneCallTracker.VtDataUsageProvider;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 
@@ -109,9 +136,12 @@
 import org.junit.Test;
 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.Set;
 import java.util.concurrent.Executor;
 
@@ -125,6 +155,7 @@
     private ImsCall.Listener mImsCallListener;
     private ImsCall mImsCall;
     private ImsCall mSecondImsCall;
+    private ISrvccStartedCallback mSrvccStartedCallback;
     private BroadcastReceiver mBroadcastReceiver;
     private Bundle mBundle = new Bundle();
     private static final int SUB_0 = 0;
@@ -140,6 +171,8 @@
     private ImsPhoneConnection mImsPhoneConnection;
     private INetworkStatsProviderCallback mVtDataUsageProviderCb;
     private ImsPhoneCallTracker.ConnectorFactory mConnectorFactory;
+    private CommandsInterface mMockCi;
+    private DomainSelectionResolver mDomainSelectionResolver;
     private CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener;
 
     private final Executor mExecutor = Runnable::run;
@@ -201,11 +234,13 @@
         mSecondImsCall = spy(new ImsCall(mContext, mImsCallProfile));
         mImsPhoneConnectionListener = mock(ImsPhoneConnection.Listener.class);
         mImsPhoneConnection = mock(ImsPhoneConnection.class);
+        mMockCi = mock(CommandsInterface.class);
         imsCallMocking(mImsCall);
         imsCallMocking(mSecondImsCall);
         doReturn(ImsFeature.STATE_READY).when(mImsManager).getImsServiceState();
         doReturn(mImsCallProfile).when(mImsManager).createCallProfile(anyInt(), anyInt());
         mContextFixture.addSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS);
+        mDomainSelectionResolver = mock(DomainSelectionResolver.class);
 
         doReturn(new SubscriptionInfoInternal.Builder().setSimSlotIndex(0).setId(1).build())
                 .when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
@@ -244,6 +279,9 @@
             return mMockConnector;
         }).when(mConnectorFactory).create(any(), anyInt(), anyString(), any(), any());
 
+        DomainSelectionResolver.setDomainSelectionResolver(mDomainSelectionResolver);
+        doReturn(false).when(mDomainSelectionResolver).isDomainSelectionSupported();
+
         // Capture CarrierConfigChangeListener to emulate the carrier config change notification
         ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> listenerArgumentCaptor =
                 ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
@@ -357,16 +395,11 @@
     @Test
     @SmallTest
     public void testCarrierConfigLoadSubscription() throws Exception {
-        // Start with there being no subId loaded, so SubscriptionController#isActiveSubId is false
-        // as part of setup, connectionReady is called, which ends up calling
-        // updateCarrierConfiguration. Since the carrier config is not report carrier identified
-        // config, we should not see updateImsServiceConfig called yet.
         verify(mImsManager, never()).updateImsServiceConfig();
         // Send disconnected indication
         mConnectorListener.connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
 
         // Receive a subscription loaded and IMS connection ready indication.
-        doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt());
         mContextFixture.getCarrierConfigBundle().putBoolean(
                 CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
         sendCarrierConfigChanged();
@@ -382,7 +415,6 @@
     public void testCarrierConfigSentLocked() throws Exception {
         // move to ImsService unavailable state.
         mConnectorListener.connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
-        doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt());
         mContextFixture.getCarrierConfigBundle().putBoolean(
                 CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
 
@@ -409,7 +441,6 @@
         verify(mImsManager, never()).updateImsServiceConfig();
 
         // Receive a subscription loaded and IMS connection ready indication.
-        doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt());
         mContextFixture.getCarrierConfigBundle().putBoolean(
                 CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
         // CarrierConfigLoader has signalled that the carrier config has been applied for a specific
@@ -429,7 +460,6 @@
     public void testCarrierConfigSentBeforeReady() throws Exception {
         // move to ImsService unavailable state.
         mConnectorListener.connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
-        doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt());
         mContextFixture.getCarrierConfigBundle().putBoolean(
                 CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
 
@@ -450,7 +480,6 @@
         verify(mImsManager, never()).updateImsServiceConfig();
 
         // Receive a subscription loaded and IMS connection ready indication.
-        doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt());
         mContextFixture.getCarrierConfigBundle().putBoolean(
                 CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
         // CarrierConfigLoader has signalled that the carrier config has been applied for a specific
@@ -474,7 +503,6 @@
     public void testCarrierConfigSentBeforeReadyAndCrash() throws Exception {
         // move to ImsService unavailable state.
         mConnectorListener.connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
-        doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt());
         mContextFixture.getCarrierConfigBundle().putBoolean(
                 CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
 
@@ -542,7 +570,7 @@
         assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
         assertFalse(mCTUT.mRingingCall.isRinging());
         // mock a MT call
-        mMmTelListener.onIncomingCall(mock(IImsCallSession.class), Bundle.EMPTY);
+        mMmTelListener.onIncomingCall(mock(IImsCallSession.class), null, Bundle.EMPTY);
         verify(mImsPhone, times(1)).notifyNewRingingConnection((Connection) any());
         verify(mImsPhone, times(1)).notifyIncomingRing();
         assertEquals(PhoneConstants.State.RINGING, mCTUT.getState());
@@ -694,7 +722,7 @@
             ex.printStackTrace();
             Assert.fail("unexpected exception thrown" + ex.getMessage());
         }
-        mMmTelListener.onIncomingCall(mock(IImsCallSession.class), Bundle.EMPTY);
+        mMmTelListener.onIncomingCall(mock(IImsCallSession.class), null, Bundle.EMPTY);
 
         verify(mImsPhone, times(2)).notifyNewRingingConnection((Connection) any());
         verify(mImsPhone, times(2)).notifyIncomingRing();
@@ -732,7 +760,7 @@
             ex.printStackTrace();
             Assert.fail("unexpected exception thrown" + ex.getMessage());
         }
-        mMmTelListener.onIncomingCall(mock(IImsCallSession.class), Bundle.EMPTY);
+        mMmTelListener.onIncomingCall(mock(IImsCallSession.class), null, Bundle.EMPTY);
 
         verify(mImsPhone, times(2)).notifyNewRingingConnection((Connection) any());
         verify(mImsPhone, times(2)).notifyIncomingRing();
@@ -924,7 +952,7 @@
         try {
             doReturn(mSecondImsCall).when(mImsManager).takeCall(any(IImsCallSession.class),
                     any(ImsCall.Listener.class));
-            mMmTelListener.onIncomingCall(mock(IImsCallSession.class), Bundle.EMPTY);
+            mMmTelListener.onIncomingCall(mock(IImsCallSession.class), null, Bundle.EMPTY);
             mCTUT.acceptCall(ImsCallProfile.CALL_TYPE_VOICE);
         } catch (Exception ex) {
             ex.printStackTrace();
@@ -1397,6 +1425,43 @@
 
     @Test
     @SmallTest
+    public void testAutoRejectedCauses() {
+        assertEquals(DisconnectCause.INCOMING_AUTO_REJECTED, mCTUT.getDisconnectCauseFromReasonInfo(
+                new ImsReasonInfo(ImsReasonInfo.CODE_REJECT_CALL_ON_OTHER_SUB, 0),
+                Call.State.INCOMING));
+        assertEquals(DisconnectCause.INCOMING_AUTO_REJECTED, mCTUT.getDisconnectCauseFromReasonInfo(
+                new ImsReasonInfo(ImsReasonInfo.CODE_REJECT_ONGOING_E911_CALL, 0),
+                Call.State.INCOMING));
+        assertEquals(DisconnectCause.INCOMING_AUTO_REJECTED, mCTUT.getDisconnectCauseFromReasonInfo(
+                new ImsReasonInfo(ImsReasonInfo.CODE_REJECT_ONGOING_CALL_SETUP, 0),
+                Call.State.INCOMING));
+        assertEquals(DisconnectCause.INCOMING_AUTO_REJECTED, mCTUT.getDisconnectCauseFromReasonInfo(
+                new ImsReasonInfo(ImsReasonInfo.CODE_REJECT_MAX_CALL_LIMIT_REACHED, 0),
+                Call.State.INCOMING));
+        assertEquals(DisconnectCause.INCOMING_AUTO_REJECTED, mCTUT.getDisconnectCauseFromReasonInfo(
+                new ImsReasonInfo(ImsReasonInfo.CODE_REJECT_ONGOING_CALL_TRANSFER, 0),
+                Call.State.INCOMING));
+        assertEquals(DisconnectCause.INCOMING_AUTO_REJECTED, mCTUT.getDisconnectCauseFromReasonInfo(
+                new ImsReasonInfo(ImsReasonInfo.CODE_REJECT_ONGOING_CONFERENCE_CALL, 0),
+                Call.State.INCOMING));
+        assertEquals(DisconnectCause.INCOMING_AUTO_REJECTED, mCTUT.getDisconnectCauseFromReasonInfo(
+                new ImsReasonInfo(ImsReasonInfo.CODE_REJECT_ONGOING_HANDOVER, 0),
+                Call.State.INCOMING));
+        assertEquals(DisconnectCause.INCOMING_AUTO_REJECTED, mCTUT.getDisconnectCauseFromReasonInfo(
+                new ImsReasonInfo(ImsReasonInfo.CODE_REJECT_ONGOING_CALL_UPGRADE, 0),
+                Call.State.INCOMING));
+        assertEquals(DisconnectCause.INCOMING_AUTO_REJECTED, mCTUT.getDisconnectCauseFromReasonInfo(
+                new ImsReasonInfo(ImsReasonInfo.CODE_SIP_BAD_REQUEST, 0), Call.State.INCOMING));
+        assertEquals(DisconnectCause.INCOMING_AUTO_REJECTED, mCTUT.getDisconnectCauseFromReasonInfo(
+                new ImsReasonInfo(ImsReasonInfo.CODE_SIP_BAD_REQUEST, 0), Call.State.WAITING));
+        assertEquals(DisconnectCause.SERVER_ERROR, mCTUT.getDisconnectCauseFromReasonInfo(
+                new ImsReasonInfo(ImsReasonInfo.CODE_SIP_BAD_REQUEST, 0), Call.State.DIALING));
+        assertEquals(DisconnectCause.SERVER_ERROR, mCTUT.getDisconnectCauseFromReasonInfo(
+                new ImsReasonInfo(ImsReasonInfo.CODE_SIP_BAD_REQUEST, 0), Call.State.ALERTING));
+    }
+
+    @Test
+    @SmallTest
     public void testImsAlternateEmergencyDisconnect() {
         assertEquals(DisconnectCause.IMS_SIP_ALTERNATE_EMERGENCY_CALL,
                 mCTUT.getDisconnectCauseFromReasonInfo(
@@ -1439,6 +1504,45 @@
         verify(mImsPhone, never()).startOnHoldTone(nullable(Connection.class));
     }
 
+    @Test
+    @SmallTest
+    public void testSendAnbrQuery() throws Exception {
+        logd("ImsPhoneCallTracker testSendAnbrQuery");
+
+        replaceInstance(Phone.class, "mCi", mPhone, mMockCi);
+        //establish a MT call
+        testImsMTCallAccept();
+
+        ImsPhoneConnection connection = mCTUT.mForegroundCall.getFirstConnection();
+        ImsCall imsCall = connection.getImsCall();
+        imsCall.getImsCallSessionListenerProxy().callSessionSendAnbrQuery(1, 1, 24400);
+
+        verify(mMockCi, times(1)).sendAnbrQuery(eq(1), eq(1), eq(24400), any());
+
+        // Disconnecting and then Disconnected
+        mCTUT.hangup(connection);
+        mImsCallListener.onCallTerminated(imsCall,
+                new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED, 0));
+    }
+
+    @Test
+    @SmallTest
+    public void testTriggerNotifyAnbr() throws Exception {
+        logd("ImsPhoneCallTracker testTriggerNotifyAnbr");
+
+        testImsMTCallAccept();
+        ImsPhoneConnection connection = mCTUT.mForegroundCall.getFirstConnection();
+        ImsCall imsCall = connection.getImsCall();
+
+        mCTUT.triggerNotifyAnbr(1, 1, 24400);
+        verify(mImsCall, times(1)).callSessionNotifyAnbr(eq(1), eq(1), eq(24400));
+
+        // Disconnecting and then Disconnected
+        mCTUT.hangup(connection);
+        mImsCallListener.onCallTerminated(imsCall,
+                new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED, 0));
+    }
+
     /**
      * Verifies that a remote hold tone is played when the call is remotely held and the media
      * direction is inactive (i.e. the audio stream is not playing, so we should play the tone).
@@ -1589,7 +1693,9 @@
             }
         });
         ImsCall call = connection.getImsCall();
-        call.getListener().onCallMerged(call, null, false);
+        call.getListener().onCallTerminated(
+                call, new ImsReasonInfo(
+                        ImsReasonInfo.CODE_LOCAL_ENDED_BY_CONFERENCE_MERGE, 0));
         assertTrue(result[0]);
     }
 
@@ -1699,7 +1805,7 @@
         assertTrue(mCTUT.mForegroundCall.isRingbackTonePlaying());
 
         // Move the connection to the handover state.
-        mCTUT.notifySrvccState(Call.SrvccState.COMPLETED);
+        mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_COMPLETED);
 
         assertFalse(mCTUT.mForegroundCall.isRingbackTonePlaying());
     }
@@ -1730,7 +1836,7 @@
         }
 
         // Move the connection to the handover state.
-        mCTUT.notifySrvccState(Call.SrvccState.COMPLETED);
+        mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_COMPLETED);
         // Ensure we are no longer tracking hold.
         assertFalse(mCTUT.isHoldOrSwapInProgress());
     }
@@ -1742,7 +1848,7 @@
         assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
         assertFalse(mCTUT.mRingingCall.isRinging());
         // mock a MT call
-        mMmTelListener.onIncomingCall(mock(IImsCallSession.class), Bundle.EMPTY);
+        mMmTelListener.onIncomingCall(mock(IImsCallSession.class), null, Bundle.EMPTY);
         verify(mImsPhone, times(1)).notifyNewRingingConnection((Connection) any());
         verify(mImsPhone, times(1)).notifyIncomingRing();
         assertEquals(PhoneConstants.State.RINGING, mCTUT.getState());
@@ -1753,7 +1859,7 @@
         connection.addListener(mImsPhoneConnectionListener);
 
         // Move the connection to the handover state.
-        mCTUT.notifySrvccState(Call.SrvccState.COMPLETED);
+        mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_COMPLETED);
         assertEquals(1, mCTUT.mHandoverCall.getConnections().size());
 
         // No need to go through all the rigamarole of the mocked termination we normally do; we
@@ -1780,7 +1886,7 @@
         assertEquals(PhoneConstants.State.IDLE, mCTUT.getState());
         assertFalse(mCTUT.mRingingCall.isRinging());
         // mock a MT call
-        mMmTelListener.onIncomingCall(mock(IImsCallSession.class), Bundle.EMPTY);
+        mMmTelListener.onIncomingCall(mock(IImsCallSession.class), null, Bundle.EMPTY);
         verify(mImsPhone, times(1)).notifyNewRingingConnection((Connection) any());
         verify(mImsPhone, times(1)).notifyIncomingRing();
         assertEquals(PhoneConstants.State.RINGING, mCTUT.getState());
@@ -1791,7 +1897,7 @@
         connection.addListener(mImsPhoneConnectionListener);
 
         // Move the connection to the handover state.
-        mCTUT.notifySrvccState(Call.SrvccState.COMPLETED);
+        mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_COMPLETED);
         assertEquals(1, mCTUT.mHandoverCall.getConnections().size());
 
         // Make sure the tracker states it's idle.
@@ -1807,7 +1913,6 @@
     @SmallTest
     public void testConfigureRtpHeaderExtensionTypes() throws Exception {
         mConnectorListener.connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
-        doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt());
         mContextFixture.getCarrierConfigBundle().putBoolean(
                 CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL,
                 true);
@@ -1838,7 +1943,6 @@
     @SmallTest
     public void testRtpButNoSdp() throws Exception {
         mConnectorListener.connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
-        doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt());
         mContextFixture.getCarrierConfigBundle().putBoolean(
                 CarrierConfigManager.KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL,
                 true);
@@ -1868,7 +1972,6 @@
     @SmallTest
     public void testDontConfigureRtpHeaderExtensionTypes() throws Exception {
         mConnectorListener.connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
-        doReturn(true).when(mSubscriptionController).isActiveSubId(anyInt());
         sendCarrierConfigChanged();
         ImsPhoneCallTracker.Config config = new ImsPhoneCallTracker.Config();
         config.isD2DCommunicationSupported = false;
@@ -1902,7 +2005,7 @@
         startOutgoingCall();
 
         // Move the connection to the handover state.
-        mCTUT.notifySrvccState(Call.SrvccState.COMPLETED);
+        mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_COMPLETED);
 
         try {
             // When trigger CallSessionUpdated after Srvcc completes, checking no exception.
@@ -1936,6 +2039,608 @@
         ImsPhoneConnection connection2 = placeCall();
     }
 
+    @Test
+    @SmallTest
+    public void testConvertToSrvccConnectionInfoNotSupported() throws Exception {
+        // setup ImsPhoneCallTracker's mConnections
+        ImsPhoneConnection activeMO = getImsPhoneConnection(Call.State.ACTIVE, "1234", false);
+        ImsPhoneConnection heldMT = getImsPhoneConnection(Call.State.HOLDING, "5678", true);
+
+        ArrayList<ImsPhoneConnection> connections = new ArrayList<ImsPhoneConnection>();
+        replaceInstance(ImsPhoneCallTracker.class, "mConnections", mCTUT, connections);
+        connections.add(activeMO);
+        connections.add(heldMT);
+
+        ImsCallProfile activeProfile = getImsCallProfileForSrvccSync("activeCall", activeMO, false);
+        ImsCallProfile heldProfile = getImsCallProfileForSrvccSync("heldCall", heldMT, false);
+
+        // setup the response of notifySrvccStarted
+        List<SrvccCall> profiles = new ArrayList<>();
+
+        SrvccConnection[] srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles);
+        assertNull(srvccConnections);
+
+        // active call
+        SrvccCall srvccProfile = new SrvccCall(
+                "activeCall", PRECISE_CALL_STATE_ACTIVE, activeProfile);
+        profiles.add(srvccProfile);
+
+        PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
+        bundle.putIntArray(
+                CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY,
+                new int[] {});
+        mCTUT.updateCarrierConfigCache(bundle);
+
+        srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles);
+        assertNull(srvccConnections);
+    }
+
+    @Test
+    @SmallTest
+    public void testConvertToSrvccConnectionInfoBasicSrvcc() throws Exception {
+        // setup ImsPhoneCallTracker's mConnections
+        ImsPhoneConnection activeMO = getImsPhoneConnection(Call.State.ACTIVE, "1234", false);
+        ImsPhoneConnection heldMT = getImsPhoneConnection(Call.State.HOLDING, "5678", true);
+
+        ArrayList<ImsPhoneConnection> connections = new ArrayList<ImsPhoneConnection>();
+        replaceInstance(ImsPhoneCallTracker.class, "mConnections", mCTUT, connections);
+        connections.add(activeMO);
+        connections.add(heldMT);
+
+        ImsCallProfile activeProfile = getImsCallProfileForSrvccSync("activeCall", activeMO, false);
+        ImsCallProfile heldProfile = getImsCallProfileForSrvccSync("heldCall", heldMT, false);
+
+        // setup the response of notifySrvccStarted
+        List<SrvccCall> profiles = new ArrayList<>();
+
+        // active call
+        SrvccCall srvccProfile = new SrvccCall(
+                "activeCall", PRECISE_CALL_STATE_ACTIVE, activeProfile);
+        profiles.add(srvccProfile);
+
+        PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
+        bundle.putIntArray(
+                CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY,
+                new int[] {
+                        BASIC_SRVCC_SUPPORT,
+                });
+        mCTUT.updateCarrierConfigCache(bundle);
+
+        SrvccConnection[] srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles);
+        assertNotNull(srvccConnections);
+        assertTrue(srvccConnections.length == 1);
+        assertTrue(srvccConnections[0].getState() == Call.State.ACTIVE);
+        assertEquals("1234", srvccConnections[0].getNumber());
+    }
+
+    @Test
+    @SmallTest
+    public void testConvertToSrvccConnectionInfoMoAlerting() throws Exception {
+        // setup ImsPhoneCallTracker's mConnections
+        ImsPhoneConnection alertingMO = getImsPhoneConnection(Call.State.ALERTING, "1234", false);
+
+        ArrayList<ImsPhoneConnection> connections = new ArrayList<ImsPhoneConnection>();
+        replaceInstance(ImsPhoneCallTracker.class, "mConnections", mCTUT, connections);
+        connections.add(alertingMO);
+
+        ImsCallProfile alertingProfile = getImsCallProfileForSrvccSync("alertingCall", null, true);
+
+        // setup the response of notifySrvccStarted
+        List<SrvccCall> profiles = new ArrayList<>();
+
+        // alerting call, with local ringback tone
+        SrvccCall srvccProfile = new SrvccCall(
+                "alertingCall", PRECISE_CALL_STATE_ALERTING, alertingProfile);
+        profiles.add(srvccProfile);
+
+        PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
+        bundle.putIntArray(
+                CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY,
+                new int[] {
+                        BASIC_SRVCC_SUPPORT,
+                });
+        mCTUT.updateCarrierConfigCache(bundle);
+
+        SrvccConnection[] srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles);
+        assertNull(srvccConnections);
+
+        bundle = mContextFixture.getCarrierConfigBundle();
+        bundle.putIntArray(
+                CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY,
+                new int[] {
+                        BASIC_SRVCC_SUPPORT,
+                        ALERTING_SRVCC_SUPPORT,
+                });
+        mCTUT.updateCarrierConfigCache(bundle);
+
+        srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles);
+        assertNotNull(srvccConnections);
+        assertTrue(srvccConnections.length == 1);
+        assertTrue(srvccConnections[0].getState() == Call.State.ALERTING);
+        assertTrue(srvccConnections[0].getRingbackToneType() == SrvccConnection.TONE_LOCAL);
+
+        profiles.clear();
+
+        // alerting call, with network ringback tone
+        alertingProfile = getImsCallProfileForSrvccSync("alertingCall", null, false);
+
+        srvccProfile = new SrvccCall(
+                "alertingCall", PRECISE_CALL_STATE_ALERTING, alertingProfile);
+        profiles.add(srvccProfile);
+
+        srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles);
+        assertNotNull(srvccConnections);
+        assertTrue(srvccConnections.length == 1);
+        assertTrue(srvccConnections[0].getState() == Call.State.ALERTING);
+        assertTrue(srvccConnections[0].getRingbackToneType() == SrvccConnection.TONE_NETWORK);
+    }
+
+    @Test
+    @SmallTest
+    public void testConvertToSrvccConnectionInfoMtAlerting() throws Exception {
+        // setup ImsPhoneCallTracker's mConnections
+        ImsPhoneConnection alertingMT = getImsPhoneConnection(Call.State.INCOMING, "1234", false);
+
+        ArrayList<ImsPhoneConnection> connections = new ArrayList<ImsPhoneConnection>();
+        replaceInstance(ImsPhoneCallTracker.class, "mConnections", mCTUT, connections);
+        connections.add(alertingMT);
+
+        ImsCallProfile incomingProfile =
+                getImsCallProfileForSrvccSync("incomingCall", alertingMT, false);
+
+        // setup the response of notifySrvccStarted
+        List<SrvccCall> profiles = new ArrayList<>();
+
+        SrvccCall srvccProfile = new SrvccCall(
+                "incomingCall", PRECISE_CALL_STATE_INCOMING, incomingProfile);
+        profiles.add(srvccProfile);
+
+        PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
+        bundle.putIntArray(
+                CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY,
+                new int[] {
+                        BASIC_SRVCC_SUPPORT,
+                });
+        mCTUT.updateCarrierConfigCache(bundle);
+
+        SrvccConnection[] srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles);
+        assertNull(srvccConnections);
+
+        bundle = mContextFixture.getCarrierConfigBundle();
+        bundle.putIntArray(
+                CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY,
+                new int[] {
+                        ALERTING_SRVCC_SUPPORT,
+                });
+        mCTUT.updateCarrierConfigCache(bundle);
+
+        srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles);
+        assertNotNull(srvccConnections);
+        assertTrue(srvccConnections.length == 1);
+        assertTrue(srvccConnections[0].getState() == Call.State.INCOMING);
+    }
+
+    @Test
+    @SmallTest
+    public void testConvertToSrvccConnectionInfoMtPreAlerting() throws Exception {
+        // setup the response of notifySrvccStarted
+        List<SrvccCall> profiles = new ArrayList<>();
+
+        ImsCallProfile incomingProfile = getImsCallProfileForSrvccSync("incomingCall", null, false);
+
+        SrvccCall srvccProfile = new SrvccCall(
+                "incomingCallSetup", PRECISE_CALL_STATE_INCOMING_SETUP, incomingProfile);
+        profiles.add(srvccProfile);
+
+        PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
+        bundle.putIntArray(
+                CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY,
+                new int[] {
+                        BASIC_SRVCC_SUPPORT,
+                        ALERTING_SRVCC_SUPPORT,
+                });
+        mCTUT.updateCarrierConfigCache(bundle);
+
+        SrvccConnection[] srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles);
+        assertNull(srvccConnections);
+
+        bundle = mContextFixture.getCarrierConfigBundle();
+        bundle.putIntArray(
+                CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY,
+                new int[] {
+                        BASIC_SRVCC_SUPPORT,
+                        ALERTING_SRVCC_SUPPORT,
+                        PREALERTING_SRVCC_SUPPORT,
+                });
+        mCTUT.updateCarrierConfigCache(bundle);
+
+        srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles);
+        assertNotNull(srvccConnections);
+        assertTrue(srvccConnections.length == 1);
+        assertTrue(srvccConnections[0].getState() == Call.State.INCOMING);
+        assertTrue(srvccConnections[0].getSubState() == SrvccConnection.SUBSTATE_PREALERTING);
+    }
+
+    @Test
+    @SmallTest
+    public void testNotifySrvccStateStarted() throws Exception {
+        PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
+        bundle.putIntArray(
+                CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY,
+                new int[] {
+                        BASIC_SRVCC_SUPPORT,
+                });
+        mCTUT.updateCarrierConfigCache(bundle);
+
+        mSrvccStartedCallback = null;
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                mSrvccStartedCallback = (ISrvccStartedCallback) invocation.getArguments()[0];
+                return null;
+            }
+        }).when(mImsManager).notifySrvccStarted(any(ISrvccStartedCallback.class));
+
+        verify(mImsManager, times(0)).notifySrvccStarted(any());
+        mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_STARTED);
+        verify(mImsManager, times(1)).notifySrvccStarted(any());
+        assertNotNull(mSrvccStartedCallback);
+
+        // setup ImsPhoneCallTracker's mConnections
+        ImsPhoneConnection activeMO = getImsPhoneConnection(Call.State.ACTIVE, "1234", false);
+
+        ArrayList<ImsPhoneConnection> connections = new ArrayList<ImsPhoneConnection>();
+        replaceInstance(ImsPhoneCallTracker.class, "mConnections", mCTUT, connections);
+        connections.add(activeMO);
+
+        ImsCallProfile activeProfile = getImsCallProfileForSrvccSync("activeCall", activeMO, false);
+
+        // setup the response of notifySrvccStarted
+        List<SrvccCall> profiles = new ArrayList<>();
+
+        // active call
+        SrvccCall srvccProfile = new SrvccCall(
+                "activeCall", PRECISE_CALL_STATE_ACTIVE, activeProfile);
+        profiles.add(srvccProfile);
+
+        mSrvccStartedCallback.onSrvccCallNotified(profiles);
+        SrvccConnection[] srvccConnections = mSimulatedCommands.getSrvccConnections();
+
+        assertNotNull(srvccConnections);
+        assertTrue(srvccConnections.length == 1);
+        assertTrue(srvccConnections[0].getState() == Call.State.ACTIVE);
+        assertEquals("1234", srvccConnections[0].getNumber());
+    }
+
+    @Test
+    @SmallTest
+    public void testNotifySrvccStateFailed() throws Exception {
+        verify(mImsManager, times(0)).notifySrvccFailed();
+        mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_FAILED);
+        verify(mImsManager, times(1)).notifySrvccFailed();
+    }
+
+    @Test
+    @SmallTest
+    public void testNotifySrvccStateCanceled() throws Exception {
+        verify(mImsManager, times(0)).notifySrvccCanceled();
+        mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_CANCELED);
+        verify(mImsManager, times(1)).notifySrvccCanceled();
+    }
+
+    @Test
+    @SmallTest
+    public void testNotifySrvccStateCompleted() throws Exception {
+        verify(mImsManager, times(0)).notifySrvccCompleted();
+        mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_COMPLETED);
+        verify(mImsManager, times(1)).notifySrvccCompleted();
+    }
+
+    @Test
+    @SmallTest
+    public void testConvertToSrvccConnectionInfoConferenceCall() throws Exception {
+        // setup ImsPhoneCallTracker's mConnections
+        ImsPhoneConnection activeMO = getImsPhoneConnection(Call.State.ACTIVE, "1234", false);
+
+        ArrayList<ImsPhoneConnection> connections = new ArrayList<ImsPhoneConnection>();
+        replaceInstance(ImsPhoneCallTracker.class, "mConnections", mCTUT, connections);
+        connections.add(activeMO);
+
+        List<ConferenceParticipant> participants = new ArrayList<ConferenceParticipant>();
+        participants.add(new ConferenceParticipant(Uri.parse("tel:1234"), "", null,
+                  android.telecom.Connection.STATE_ACTIVE,
+                  android.telecom.Call.Details.DIRECTION_INCOMING));
+        participants.add(new ConferenceParticipant(Uri.parse("tel:5678"), "", null,
+                  android.telecom.Connection.STATE_ACTIVE,
+                  android.telecom.Call.Details.DIRECTION_OUTGOING));
+
+        ImsCallProfile activeProfile = getImsCallProfileForSrvccSync("activeCall",
+                activeMO, false, participants);
+
+        // setup the response of notifySrvccStarted
+        List<SrvccCall> profiles = new ArrayList<>();
+
+        SrvccConnection[] srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles);
+        assertNull(srvccConnections);
+
+        // active call
+        SrvccCall srvccProfile = new SrvccCall(
+                "activeCall", PRECISE_CALL_STATE_ACTIVE, activeProfile);
+        profiles.add(srvccProfile);
+
+        PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
+        bundle.putIntArray(
+                CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY,
+                new int[] {
+                        BASIC_SRVCC_SUPPORT,
+                });
+        mCTUT.updateCarrierConfigCache(bundle);
+
+        srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles);
+        assertNotNull(srvccConnections);
+        assertTrue(srvccConnections.length == 1);
+        assertTrue(srvccConnections[0].getState() == Call.State.ACTIVE);
+        assertFalse(srvccConnections[0].isMultiParty());
+        assertEquals("1234", srvccConnections[0].getNumber());
+
+        bundle = mContextFixture.getCarrierConfigBundle();
+        bundle.putIntArray(
+                CarrierConfigManager.ImsVoice.KEY_SRVCC_TYPE_INT_ARRAY,
+                new int[] {
+                        BASIC_SRVCC_SUPPORT,
+                        MIDCALL_SRVCC_SUPPORT
+                });
+        mCTUT.updateCarrierConfigCache(bundle);
+
+        srvccConnections = mCTUT.convertToSrvccConnectionInfo(profiles);
+        assertNotNull(srvccConnections);
+        assertTrue(srvccConnections.length == 2);
+
+        assertTrue(srvccConnections[0].getState() == Call.State.ACTIVE);
+        assertTrue(srvccConnections[0].isMultiParty());
+        assertTrue(srvccConnections[0].isIncoming());
+        assertEquals("1234", srvccConnections[0].getNumber());
+
+        assertTrue(srvccConnections[1].getState() == Call.State.ACTIVE);
+        assertTrue(srvccConnections[1].isMultiParty());
+        assertFalse(srvccConnections[1].isIncoming());
+        assertEquals("5678", srvccConnections[1].getNumber());
+    }
+
+    /**
+     * Verifies that the expected access network tech and IMS features are notified
+     * to ImsPhone when capabilities are changed.
+     */
+    @Test
+    @SmallTest
+    public void testUpdateImsRegistrationInfo() {
+        // LTE is registered.
+        doReturn(ImsRegistrationImplBase.REGISTRATION_TECH_LTE).when(
+                mImsManager).getRegistrationTech();
+
+        // enable Voice and Video
+        MmTelFeature.MmTelCapabilities caps = new MmTelFeature.MmTelCapabilities();
+        caps.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
+        caps.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO);
+        mCapabilityCallback.onCapabilitiesStatusChanged(caps);
+        processAllMessages();
+
+        verify(mImsPhone, times(1)).updateImsRegistrationInfo(
+                eq(CommandsInterface.IMS_MMTEL_CAPABILITY_VOICE
+                        | CommandsInterface.IMS_MMTEL_CAPABILITY_VIDEO));
+
+        // enable SMS
+        caps = new MmTelFeature.MmTelCapabilities();
+        caps.addCapabilities(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_SMS);
+        mCapabilityCallback.onCapabilitiesStatusChanged(caps);
+        processAllMessages();
+
+        verify(mImsPhone, times(1)).updateImsRegistrationInfo(
+                eq(CommandsInterface.IMS_MMTEL_CAPABILITY_SMS));
+    }
+
+    @Test
+    @SmallTest
+    public void testDomainSelectionAlternateServiceStartFailed() {
+        doReturn(true).when(mDomainSelectionResolver).isDomainSelectionSupported();
+        startOutgoingCall();
+        ImsPhoneConnection c = mCTUT.mForegroundCall.getFirstConnection();
+        mImsCallProfile.setEmergencyServiceCategories(EMERGENCY_SERVICE_CATEGORY_AMBULANCE);
+        mImsCallListener.onCallStartFailed(mSecondImsCall,
+                new ImsReasonInfo(ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL, -1));
+        processAllMessages();
+        EmergencyNumber emergencyNumber = c.getEmergencyNumberInfo();
+        assertNotNull(emergencyNumber);
+        assertEquals(EMERGENCY_SERVICE_CATEGORY_AMBULANCE,
+                emergencyNumber.getEmergencyServiceCategoryBitmask());
+    }
+
+    @Test
+    @SmallTest
+    public void testDomainSelectionAlternateServiceStartFailedNullPendingMO() {
+        doReturn(true).when(mDomainSelectionResolver).isDomainSelectionSupported();
+        startOutgoingCall();
+        ImsPhoneConnection c = mCTUT.mForegroundCall.getFirstConnection();
+        mImsCallListener.onCallProgressing(mSecondImsCall);
+        processAllMessages();
+        mImsCallProfile.setEmergencyServiceCategories(EMERGENCY_SERVICE_CATEGORY_AMBULANCE);
+        mImsCallListener.onCallStartFailed(mSecondImsCall,
+                new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED,
+                        ImsReasonInfo.EXTRA_CODE_CALL_RETRY_EMERGENCY));
+        processAllMessages();
+        EmergencyNumber emergencyNumber = c.getEmergencyNumberInfo();
+        assertNotNull(emergencyNumber);
+        assertEquals(EMERGENCY_SERVICE_CATEGORY_AMBULANCE,
+                emergencyNumber.getEmergencyServiceCategoryBitmask());
+    }
+
+    @Test
+    @SmallTest
+    public void testDomainSelectionAlternateServiceTerminated() {
+        doReturn(true).when(mDomainSelectionResolver).isDomainSelectionSupported();
+        startOutgoingCall();
+        ImsPhoneConnection c = mCTUT.mForegroundCall.getFirstConnection();
+        mImsCallProfile.setEmergencyServiceCategories(EMERGENCY_SERVICE_CATEGORY_AMBULANCE);
+        mImsCallListener.onCallTerminated(mSecondImsCall,
+                new ImsReasonInfo(ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL, -1));
+        processAllMessages();
+        EmergencyNumber emergencyNumber = c.getEmergencyNumberInfo();
+        assertNotNull(emergencyNumber);
+        assertEquals(EMERGENCY_SERVICE_CATEGORY_AMBULANCE,
+                emergencyNumber.getEmergencyServiceCategoryBitmask());
+    }
+
+    @Test
+    public void testUpdateImsCallStatusIncoming() throws Exception {
+        // Incoming call
+        ImsPhoneConnection connection = setupRingingConnection();
+
+        verify(mImsPhone, times(1)).updateImsCallStatus(any(), any());
+
+        // Disconnect the call
+        mImsCallListener.onCallTerminated(connection.getImsCall(),
+                new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE, 0));
+
+        verify(mImsPhone, times(2)).updateImsCallStatus(any(), any());
+    }
+
+    @Test
+    public void testUpdateImsCallStatus() throws Exception {
+        // Dialing
+        ImsPhoneConnection connection = placeCall();
+
+        verify(mImsPhone, times(1)).updateImsCallStatus(any(), any());
+
+        // Alerting
+        ImsCall imsCall = connection.getImsCall();
+        imsCall.getImsCallSessionListenerProxy().callSessionProgressing(imsCall.getSession(),
+                new ImsStreamMediaProfile());
+
+        verify(mImsPhone, times(2)).updateImsCallStatus(any(), any());
+
+        // Active
+        imsCall.getImsCallSessionListenerProxy().callSessionStarted(imsCall.getSession(),
+                new ImsCallProfile());
+
+        verify(mImsPhone, times(3)).updateImsCallStatus(any(), any());
+
+        // Held by remote
+        mCTUT.onCallHoldReceived(imsCall);
+
+        verify(mImsPhone, times(4)).updateImsCallStatus(any(), any());
+
+        // Resumed by remote
+        mImsCallListener.onCallResumeReceived(imsCall);
+
+        verify(mImsPhone, times(5)).updateImsCallStatus(any(), any());
+
+        // Disconnecting and then Disconnected
+        mCTUT.hangup(connection);
+        mImsCallListener.onCallTerminated(imsCall,
+                new ImsReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED, 0));
+
+        verify(mImsPhone, times(7)).updateImsCallStatus(any(), any());
+    }
+
+    @Test
+    public void testUpdateImsCallStatusSrvccCompleted() throws Exception {
+        // Incoming call
+        setupRingingConnection();
+
+        verify(mImsPhone, times(1)).updateImsCallStatus(any(), any());
+
+        // no interaction when SRVCC has started, failed, or canceled.
+        mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_STARTED);
+
+        verify(mImsPhone, times(1)).updateImsCallStatus(any(), any());
+
+        mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_FAILED);
+
+        verify(mImsPhone, times(1)).updateImsCallStatus(any(), any());
+
+        mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_CANCELED);
+
+        verify(mImsPhone, times(1)).updateImsCallStatus(any(), any());
+
+        // interaction when SRVCC has completed
+        mCTUT.notifySrvccState(SRVCC_STATE_HANDOVER_COMPLETED);
+
+        verify(mImsPhone, times(2)).updateImsCallStatus(any(), any());
+    }
+
+    @Test
+    public void testClearAllOrphanedConnectionInfo() throws Exception {
+        verify(mImsPhone, times(0)).updateImsCallStatus(any(), any());
+
+        mConnectorListener.connectionUnavailable(FeatureConnector.UNAVAILABLE_REASON_DISCONNECTED);
+
+        verify(mImsPhone, times(1)).updateImsCallStatus(any(), any());
+    }
+
+    /** Verifies that the request from ImsService is passed to ImsPhone as expected. */
+    @Test
+    @SmallTest
+    public void testStartAndStopImsTrafficSession() {
+        IImsTrafficSessionCallback binder = Mockito.mock(IImsTrafficSessionCallback.class);
+        mMmTelListener.onStartImsTrafficSession(1, MmTelFeature.IMS_TRAFFIC_TYPE_EMERGENCY,
+                AccessNetworkConstants.AccessNetworkType.EUTRAN,
+                IMS_TRAFFIC_DIRECTION_OUTGOING, binder);
+        verify(mImsPhone, times(1)).startImsTraffic(eq(1),
+                eq(MmTelFeature.IMS_TRAFFIC_TYPE_EMERGENCY),
+                eq(AccessNetworkConstants.AccessNetworkType.EUTRAN),
+                eq(IMS_TRAFFIC_DIRECTION_OUTGOING), any());
+
+        mMmTelListener.onStopImsTrafficSession(1);
+        verify(mImsPhone, times(1)).stopImsTraffic(eq(1), any());
+
+        mMmTelListener.onStartImsTrafficSession(2, MmTelFeature.IMS_TRAFFIC_TYPE_EMERGENCY_SMS,
+                AccessNetworkConstants.AccessNetworkType.IWLAN,
+                IMS_TRAFFIC_DIRECTION_OUTGOING, binder);
+        verify(mImsPhone, times(1)).startImsTraffic(eq(2),
+                eq(MmTelFeature.IMS_TRAFFIC_TYPE_EMERGENCY_SMS),
+                eq(AccessNetworkConstants.AccessNetworkType.IWLAN),
+                eq(IMS_TRAFFIC_DIRECTION_OUTGOING), any());
+
+        mMmTelListener.onStartImsTrafficSession(3, MmTelFeature.IMS_TRAFFIC_TYPE_VOICE,
+                AccessNetworkConstants.AccessNetworkType.EUTRAN,
+                IMS_TRAFFIC_DIRECTION_INCOMING, binder);
+        verify(mImsPhone, times(1)).startImsTraffic(eq(3),
+                eq(MmTelFeature.IMS_TRAFFIC_TYPE_VOICE),
+                eq(AccessNetworkConstants.AccessNetworkType.EUTRAN),
+                eq(IMS_TRAFFIC_DIRECTION_INCOMING), any());
+
+        mMmTelListener.onStartImsTrafficSession(4, MmTelFeature.IMS_TRAFFIC_TYPE_VIDEO,
+                AccessNetworkConstants.AccessNetworkType.EUTRAN,
+                IMS_TRAFFIC_DIRECTION_OUTGOING, binder);
+        verify(mImsPhone, times(1)).startImsTraffic(eq(4),
+                eq(MmTelFeature.IMS_TRAFFIC_TYPE_VIDEO),
+                eq(AccessNetworkConstants.AccessNetworkType.EUTRAN),
+                eq(IMS_TRAFFIC_DIRECTION_OUTGOING), any());
+
+        mMmTelListener.onStartImsTrafficSession(5, MmTelFeature.IMS_TRAFFIC_TYPE_SMS,
+                AccessNetworkConstants.AccessNetworkType.EUTRAN,
+                IMS_TRAFFIC_DIRECTION_OUTGOING, binder);
+        verify(mImsPhone, times(1)).startImsTraffic(eq(5),
+                eq(MmTelFeature.IMS_TRAFFIC_TYPE_SMS),
+                eq(AccessNetworkConstants.AccessNetworkType.EUTRAN),
+                eq(IMS_TRAFFIC_DIRECTION_OUTGOING), any());
+
+        mMmTelListener.onStartImsTrafficSession(6, MmTelFeature.IMS_TRAFFIC_TYPE_REGISTRATION,
+                AccessNetworkConstants.AccessNetworkType.EUTRAN,
+                IMS_TRAFFIC_DIRECTION_OUTGOING, binder);
+        verify(mImsPhone, times(1)).startImsTraffic(eq(6),
+                eq(MmTelFeature.IMS_TRAFFIC_TYPE_REGISTRATION),
+                eq(AccessNetworkConstants.AccessNetworkType.EUTRAN),
+                eq(IMS_TRAFFIC_DIRECTION_OUTGOING), any());
+
+        mMmTelListener.onModifyImsTrafficSession(6,
+                AccessNetworkConstants.AccessNetworkType.IWLAN);
+        verify(mImsPhone, times(1)).startImsTraffic(eq(6),
+                eq(MmTelFeature.IMS_TRAFFIC_TYPE_REGISTRATION),
+                eq(AccessNetworkConstants.AccessNetworkType.IWLAN),
+                eq(IMS_TRAFFIC_DIRECTION_OUTGOING), any());
+    }
+
     private void sendCarrierConfigChanged() {
         mCarrierConfigChangeListener.onCarrierConfigChanged(mPhone.getPhoneId(), mPhone.getSubId(),
                 TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
@@ -2009,5 +2714,45 @@
         }
         return connection;
     }
+
+    private ImsPhoneConnection getImsPhoneConnection(Call.State state,
+            String number, boolean isIncoming) {
+        ImsPhoneCall call = mock(ImsPhoneCall.class);
+        doReturn(state).when(call).getState();
+
+        ImsPhoneConnection c = mock(ImsPhoneConnection.class);
+        doReturn(state).when(c).getState();
+        doReturn(isIncoming).when(c).isIncoming();
+        doReturn(call).when(c).getCall();
+        doReturn(number).when(c).getAddress();
+
+        return c;
+    }
+
+    private ImsCallProfile getImsCallProfileForSrvccSync(String callId,
+            ImsPhoneConnection c, boolean localTone) {
+        return getImsCallProfileForSrvccSync(callId, c, localTone, null);
+    }
+
+    private ImsCallProfile getImsCallProfileForSrvccSync(String callId,
+            ImsPhoneConnection c, boolean localTone, List<ConferenceParticipant> participants) {
+        ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(0,
+                localTone ? DIRECTION_INACTIVE : DIRECTION_SEND_RECEIVE, 0, 0, 0);
+        ImsCallProfile profile = new ImsCallProfile(0, 0, null, mediaProfile);
+
+        if (c != null) {
+            ImsCallSession session = mock(ImsCallSession.class);
+            doReturn(callId).when(session).getCallId();
+
+            ImsCall imsCall = mock(ImsCall.class);
+            doReturn(profile).when(imsCall).getCallProfile();
+            doReturn(session).when(imsCall).getCallSession();
+            doReturn(participants).when(imsCall).getConferenceParticipants();
+
+            doReturn(imsCall).when(c).getImsCall();
+        }
+
+        return profile;
+    }
 }
 
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 9779cfa..396466c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneConnectionTest.java
@@ -58,6 +58,7 @@
 import com.android.internal.telephony.GsmCdmaCall;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.imsphone.ImsPhone.ImsDialArgs;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.metrics.VoiceCallSessionStats;
 
@@ -144,7 +145,8 @@
 
         logd("Testing initial state of MO ImsPhoneConnection");
         mConnectionUT = new ImsPhoneConnection(mImsPhone, String.format("+1 (700).555-41NN%c1234",
-                PhoneNumberUtils.PAUSE), mImsCT, mForeGroundCall, false, false);
+                PhoneNumberUtils.PAUSE), mImsCT, mForeGroundCall, false, false,
+                new ImsDialArgs.Builder().build());
         assertEquals(PhoneConstants.PRESENTATION_ALLOWED, mConnectionUT.getNumberPresentation());
         assertEquals(PhoneConstants.PRESENTATION_ALLOWED, mConnectionUT.getCnapNamePresentation());
         assertEquals("+1 (700).555-41NN,1234", mConnectionUT.getOrigDialString());
@@ -157,7 +159,7 @@
     public void testImsUpdateStateForeGround() {
         // MO Foreground Connection dailing -> active
         mConnectionUT = new ImsPhoneConnection(mImsPhone, "+1 (700).555-41NN1234", mImsCT,
-                mForeGroundCall, false, false);
+                mForeGroundCall, false, false, new ImsDialArgs.Builder().build());
         // initially in dialing state
         doReturn(Call.State.DIALING).when(mForeGroundCall).getState();
         assertTrue(mConnectionUT.update(mImsCall, Call.State.ACTIVE));
@@ -172,7 +174,7 @@
     public void testUpdateCodec() {
         // MO Foreground Connection dailing -> active
         mConnectionUT = new ImsPhoneConnection(mImsPhone, "+1 (700).555-41NN1234", mImsCT,
-                mForeGroundCall, false, false);
+                mForeGroundCall, false, false, new ImsDialArgs.Builder().build());
         doReturn(Call.State.ACTIVE).when(mForeGroundCall).getState();
         assertTrue(mConnectionUT.updateMediaCapabilities(mImsCall));
     }
@@ -195,7 +197,7 @@
     @SmallTest
     public void testImsUpdateStatePendingHold() {
         mConnectionUT = new ImsPhoneConnection(mImsPhone, "+1 (700).555-41NN1234", mImsCT,
-                mForeGroundCall, false, false);
+                mForeGroundCall, false, false, new ImsDialArgs.Builder().build());
         doReturn(true).when(mImsCall).isPendingHold();
         assertFalse(mConnectionUT.update(mImsCall, Call.State.ACTIVE));
         verify(mForeGroundCall, times(0)).update(eq(mConnectionUT), eq(mImsCall),
@@ -240,7 +242,8 @@
     @SmallTest
     public void testPostDialWait() {
         mConnectionUT = new ImsPhoneConnection(mImsPhone, String.format("+1 (700).555-41NN%c1234",
-                PhoneNumberUtils.WAIT), mImsCT, mForeGroundCall, false, false);
+                PhoneNumberUtils.WAIT), mImsCT, mForeGroundCall, false, false,
+                new ImsDialArgs.Builder().build());
         doReturn(Call.State.DIALING).when(mForeGroundCall).getState();
         doAnswer(new Answer() {
             @Override
@@ -263,7 +266,8 @@
     @MediumTest
     public void testPostDialPause() {
         mConnectionUT = new ImsPhoneConnection(mImsPhone, String.format("+1 (700).555-41NN%c1234",
-                PhoneNumberUtils.PAUSE), mImsCT, mForeGroundCall, false, false);
+                PhoneNumberUtils.PAUSE), mImsCT, mForeGroundCall, false, false,
+                new ImsDialArgs.Builder().build());
         doReturn(Call.State.DIALING).when(mForeGroundCall).getState();
         doAnswer(new Answer() {
             @Override
@@ -385,7 +389,7 @@
                 {"12345*00000", "12346", "12346"}};
         for (String[] testAddress : testAddressMappingSet) {
             mConnectionUT = new ImsPhoneConnection(mImsPhone, testAddress[0], mImsCT,
-                    mForeGroundCall, false, false);
+                    mForeGroundCall, false, false, new ImsDialArgs.Builder().build());
             mConnectionUT.setIsIncoming(true);
             mImsCallProfile.setCallExtra(ImsCallProfile.EXTRA_OI, testAddress[1]);
             mConnectionUT.updateAddressDisplay(mImsCall);
@@ -403,7 +407,7 @@
         String updateAddress = "6789";
 
         mConnectionUT = new ImsPhoneConnection(mImsPhone, inputAddress, mImsCT, mForeGroundCall,
-                false, false);
+                false, false, new ImsDialArgs.Builder().build());
         mConnectionUT.setIsIncoming(false);
         mImsCallProfile.setCallExtra(ImsCallProfile.EXTRA_OI, updateAddress);
         mConnectionUT.updateAddressDisplay(mImsCall);
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 3cbf8bd..d8173a2 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneMmiCodeTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneMmiCodeTest.java
@@ -29,6 +29,7 @@
 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;
 
@@ -126,6 +127,7 @@
      * Ensure that when an operation is not supported that the correct message is returned.
      */
     @Test
+    @SmallTest
     public void testOperationNotSupported() {
         mImsPhoneMmiCode = ImsPhoneMmiCode.newNetworkInitiatedUssd(null, true, mImsPhoneUT);
 
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 3c4c65d..b74c8af 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java
@@ -17,14 +17,23 @@
 package com.android.internal.telephony.imsphone;
 
 import static android.Manifest.permission.MODIFY_PHONE_STATE;
-import static android.provider.Telephony.SimInfo.COLUMN_PHONE_NUMBER_SOURCE_IMS;
 import static android.telephony.CarrierConfigManager.USSD_OVER_CS_ONLY;
 import static android.telephony.CarrierConfigManager.USSD_OVER_CS_PREFERRED;
 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_PLMN_BLOCK;
+import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT;
+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;
+import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NR;
 
 import static com.android.internal.telephony.CommandsInterface.CF_ACTION_ENABLE;
 import static com.android.internal.telephony.CommandsInterface.CF_REASON_UNCONDITIONAL;
+import static com.android.internal.telephony.CommandsInterface.IMS_MMTEL_CAPABILITY_SMS;
+import static com.android.internal.telephony.CommandsInterface.IMS_MMTEL_CAPABILITY_VIDEO;
+import static com.android.internal.telephony.CommandsInterface.IMS_MMTEL_CAPABILITY_VOICE;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -66,6 +75,7 @@
 import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsCallProfile;
 import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsRegistrationAttributes;
 import android.telephony.ims.RegistrationManager;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.telephony.ims.stub.ImsUtImplBase;
@@ -85,6 +95,7 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.domainselection.DomainSelectionResolver;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
 import com.android.internal.telephony.imsphone.ImsPhone.SS;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
@@ -110,6 +121,7 @@
     private ImsPhoneCall mBackgroundCall;
     private ImsPhoneCall mRingingCall;
     private Handler mTestHandler;
+    private DomainSelectionResolver mDomainSelectionResolver;
     Connection mConnection;
     ImsUtInterface mImsUtInterface;
 
@@ -135,6 +147,9 @@
         mTestHandler = mock(Handler.class);
         mConnection = mock(Connection.class);
         mImsUtInterface = mock(ImsUtInterface.class);
+        mDomainSelectionResolver = mock(DomainSelectionResolver.class);
+        doReturn(false).when(mDomainSelectionResolver).isDomainSelectionSupported();
+        DomainSelectionResolver.setDomainSelectionResolver(mDomainSelectionResolver);
 
         mImsCT.mForegroundCall = mForegroundCall;
         mImsCT.mBackgroundCall = mBackgroundCall;
@@ -178,6 +193,7 @@
     public void tearDown() throws Exception {
         mImsPhoneUT = null;
         mBundle = null;
+        DomainSelectionResolver.setDomainSelectionResolver(null);
         super.tearDown();
     }
 
@@ -631,6 +647,21 @@
 
     @Test
     @SmallTest
+    public void testEcbmWhenDomainSelectionEnabled() {
+        DomainSelectionResolver dsResolver = mock(DomainSelectionResolver.class);
+        doReturn(true).when(dsResolver).isDomainSelectionSupported();
+        DomainSelectionResolver.setDomainSelectionResolver(dsResolver);
+
+        ImsEcbmStateListener imsEcbmStateListener = mImsPhoneUT.getImsEcbmStateListener();
+        imsEcbmStateListener.onECBMEntered();
+        imsEcbmStateListener.onECBMExited();
+
+        verify(mPhone, never()).setIsInEcm(anyBoolean());
+        verify(mContext, never()).sendStickyBroadcastAsUser(any(), any());
+    }
+
+    @Test
+    @SmallTest
     public void testProcessDisconnectReason() throws Exception {
         // set up CarrierConfig
         mBundle.putStringArray(CarrierConfigManager.KEY_WFC_OPERATOR_ERROR_CODES_STRING_ARRAY,
@@ -736,7 +767,6 @@
     @Test
     @SmallTest
     public void testRoamingToAirplanModeIwlanInService() throws Exception {
-        doReturn(true).when(mAccessNetworksManager).isInLegacyMode();
         doReturn(PhoneConstants.State.IDLE).when(mImsCT).getState();
         doReturn(true).when(mPhone).isRadioOn();
 
@@ -764,7 +794,6 @@
     @Test
     @SmallTest
     public void testRoamingToOutOfService() throws Exception {
-        doReturn(true).when(mAccessNetworksManager).isInLegacyMode();
         doReturn(PhoneConstants.State.IDLE).when(mImsCT).getState();
         doReturn(true).when(mPhone).isRadioOn();
 
@@ -788,83 +817,6 @@
     }
 
     @Test
-    @SmallTest
-    public void testRoamingChangeForLteInLegacyMode() throws Exception {
-        doReturn(true).when(mAccessNetworksManager).isInLegacyMode();
-        doReturn(PhoneConstants.State.IDLE).when(mImsCT).getState();
-        doReturn(true).when(mPhone).isRadioOn();
-
-        //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, times(1)).setWfcMode(anyInt(), eq(true));
-
-        // not roaming - data registration on LTE
-        m = getServiceStateChangedMessage(getServiceStateDataOnly(
-                ServiceState.RIL_RADIO_TECHNOLOGY_LTE, ServiceState.STATE_IN_SERVICE, false));
-        mImsPhoneUT.handleMessage(m);
-        m.recycle();
-
-        verify(mImsManager, times(1)).setWfcMode(anyInt(), eq(false));
-    }
-
-    @Test
-    @SmallTest
-    public void testDataOnlyRoamingCellToIWlanInLegacyMode() throws Exception {
-        doReturn(true).when(mAccessNetworksManager).isInLegacyMode();
-        doReturn(PhoneConstants.State.IDLE).when(mImsCT).getState();
-        doReturn(true).when(mPhone).isRadioOn();
-
-        //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, times(1)).setWfcMode(anyInt(), eq(true));
-
-        // not roaming - data registration onto IWLAN
-        m = getServiceStateChangedMessage(getServiceStateDataOnly(
-                ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN, ServiceState.STATE_IN_SERVICE, false));
-        mImsPhoneUT.handleMessage(m);
-        m.recycle();
-
-        // Verify that it hasn't been called again.
-        verify(mImsManager, times(1)).setWfcMode(anyInt(), anyBoolean());
-    }
-
-    @Test
-    @SmallTest
-    public void testCellVoiceDataChangeToWlanInLegacyMode() throws Exception {
-        doReturn(true).when(mAccessNetworksManager).isInLegacyMode();
-        doReturn(PhoneConstants.State.IDLE).when(mImsCT).getState();
-        doReturn(true).when(mPhone).isRadioOn();
-
-        //roaming - voice/data registration on LTE
-        ServiceState ss = getServiceStateDataAndVoice(
-                ServiceState.RIL_RADIO_TECHNOLOGY_LTE, ServiceState.STATE_IN_SERVICE, true);
-        Message m = getServiceStateChangedMessage(ss);
-        // Inject the message synchronously instead of waiting for the thread to do it.
-        mImsPhoneUT.handleMessage(m);
-
-        verify(mImsManager, times(1)).setWfcMode(anyInt(), eq(true));
-
-        // roaming - voice LTE, data registration onto IWLAN
-        modifyServiceStateData(ss, ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN,
-                ServiceState.STATE_IN_SERVICE, false);
-        mImsPhoneUT.handleMessage(m);
-        m.recycle();
-
-        // Verify that it hasn't been called again.
-        verify(mImsManager, times(1)).setWfcMode(anyInt(), anyBoolean());
-    }
-
-    @Test
     public void testNonNullTrackersInImsPhone() throws Exception {
         assertNotNull(mImsPhoneUT.getEmergencyNumberTracker());
         assertNotNull(mImsPhoneUT.getServiceStateTracker());
@@ -1018,7 +970,6 @@
         doReturn(subId).when(mPhone).getSubId();
         SubscriptionInfo subInfo = mock(SubscriptionInfo.class);
         doReturn("gb").when(subInfo).getCountryIso();
-        doReturn(subInfo).when(mSubscriptionController).getSubscriptionInfo(subId);
         doReturn(new SubscriptionInfoInternal.Builder().setId(subId).setSimSlotIndex(0)
                 .setCountryIso("gb").build()).when(mSubscriptionManagerService)
                 .getSubscriptionInfoInternal(subId);
@@ -1030,12 +981,7 @@
         };
         mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris);
 
-        if (isSubscriptionManagerServiceEnabled()) {
-            verify(mSubscriptionManagerService).setNumberFromIms(subId, "+447539447777");
-        } else {
-            verify(mSubscriptionController).setSubscriptionProperty(
-                    subId, COLUMN_PHONE_NUMBER_SOURCE_IMS, "+447539447777");
-        }
+        verify(mSubscriptionManagerService).setNumberFromIms(subId, "+447539447777");
 
         // 2. 1st invalid and 2nd valid: 2nd is set.
         associatedUris = new Uri[] {
@@ -1044,12 +990,7 @@
         };
         mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris);
 
-        if (isSubscriptionManagerServiceEnabled()) {
-            verify(mSubscriptionManagerService).setNumberFromIms(subId, "+447539446666");
-        } else {
-            verify(mSubscriptionController).setSubscriptionProperty(
-                    subId, COLUMN_PHONE_NUMBER_SOURCE_IMS, "+447539446666");
-        }
+        verify(mSubscriptionManagerService).setNumberFromIms(subId, "+447539446666");
 
         // 3. 1st sip-uri is not phone number and 2nd valid: 2nd is set.
         associatedUris = new Uri[] {
@@ -1059,12 +1000,7 @@
         };
         mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris);
 
-        if (isSubscriptionManagerServiceEnabled()) {
-            verify(mSubscriptionManagerService).setNumberFromIms(subId, "+447539446677");
-        } else {
-            verify(mSubscriptionController).setSubscriptionProperty(
-                    subId, COLUMN_PHONE_NUMBER_SOURCE_IMS, "+447539446677");
-        }
+        verify(mSubscriptionManagerService).setNumberFromIms(subId, "+447539446677");
 
         // Clean up
         mContextFixture.addCallingOrSelfPermission("");
@@ -1079,7 +1015,6 @@
         doReturn(subId).when(mPhone).getSubId();
         SubscriptionInfo subInfo = mock(SubscriptionInfo.class);
         doReturn("gb").when(subInfo).getCountryIso();
-        doReturn(subInfo).when(mSubscriptionController).getSubscriptionInfo(subId);
         doReturn(new SubscriptionInfoInternal.Builder().setId(subId).setSimSlotIndex(0)
                 .setCountryIso("gb").build()).when(mSubscriptionManagerService)
                 .getSubscriptionInfoInternal(0);
@@ -1091,49 +1026,367 @@
         };
         mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris);
 
-        if (isSubscriptionManagerServiceEnabled()) {
-            verify(mSubscriptionManagerService, never()).setNumberFromIms(anyInt(), anyString());
-        } else {
-            verify(mSubscriptionController, never()).setSubscriptionProperty(
-                    anyInt(), any(), any());
-        }
+        verify(mSubscriptionManagerService, never()).setNumberFromIms(anyInt(), anyString());
 
         // 2. no URI; do not set
         associatedUris = new Uri[] {};
         mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris);
 
-        if (isSubscriptionManagerServiceEnabled()) {
-            verify(mSubscriptionManagerService, never()).setNumberFromIms(anyInt(), anyString());
-        } else {
-            verify(mSubscriptionController, never()).setSubscriptionProperty(
-                    anyInt(), any(), any());
-        }
+        verify(mSubscriptionManagerService, never()).setNumberFromIms(anyInt(), anyString());
 
         // 3. null URI; do not set
         associatedUris = new Uri[] { null };
         mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris);
 
-        if (isSubscriptionManagerServiceEnabled()) {
-            verify(mSubscriptionManagerService, never()).setNumberFromIms(anyInt(), anyString());
-        } else {
-            verify(mSubscriptionController, never()).setSubscriptionProperty(
-                    anyInt(), any(), any());
-        }
+        verify(mSubscriptionManagerService, never()).setNumberFromIms(anyInt(), anyString());
 
         // 4. null pointer; do not set
         mImsPhoneUT.setPhoneNumberForSourceIms(null);
 
-        if (isSubscriptionManagerServiceEnabled()) {
-            verify(mSubscriptionManagerService, never()).setNumberFromIms(anyInt(), anyString());
-        } else {
-            verify(mSubscriptionController, never()).setSubscriptionProperty(
-                    anyInt(), any(), any());
-        }
+        verify(mSubscriptionManagerService, never()).setNumberFromIms(anyInt(), anyString());
 
         // Clean up
         mContextFixture.addCallingOrSelfPermission("");
     }
 
+    /**
+     * Verifies that valid radio technology is passed to RIL
+     * when IMS registration state changes to registered.
+     */
+    @Test
+    @SmallTest
+    public void testUpdateImsRegistrationInfoRadioTech() {
+        mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null);
+
+        int[] regInfo = mSimulatedCommands.getImsRegistrationInfo();
+        assertNotNull(regInfo);
+        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0);
+
+        RegistrationManager.RegistrationCallback registrationCallback =
+                mImsPhoneUT.getImsMmTelRegistrationCallback();
+
+        ImsRegistrationAttributes attr = new ImsRegistrationAttributes.Builder(
+                REGISTRATION_TECH_LTE).build();
+        registrationCallback.onRegistered(attr);
+        mImsPhoneUT.updateImsRegistrationInfo(IMS_MMTEL_CAPABILITY_VOICE);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_REGISTERED
+                && regInfo[1] == REGISTRATION_TECH_LTE
+                && regInfo[3] == IMS_MMTEL_CAPABILITY_VOICE);
+
+        // reset the registration info saved in the SimulatedCommands
+        mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0);
+
+        // duplicated notification with the same radio technology
+        attr = new ImsRegistrationAttributes.Builder(REGISTRATION_TECH_LTE).build();
+        registrationCallback.onRegistered(attr);
+
+        // verify that there is no change in SimulatedCommands
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0);
+
+        // radio technology changed
+        attr = new ImsRegistrationAttributes.Builder(REGISTRATION_TECH_NR).build();
+        registrationCallback.onRegistered(attr);
+
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+        assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_REGISTERED
+                && regInfo[1] == REGISTRATION_TECH_NR
+                && regInfo[3] == IMS_MMTEL_CAPABILITY_VOICE);
+
+        // reset the registration info saved in the SimulatedCommands
+        mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0);
+
+        // duplicated notification with the same radio technology
+        attr = new ImsRegistrationAttributes.Builder(REGISTRATION_TECH_NR).build();
+        registrationCallback.onRegistered(attr);
+
+        // verify that there is no change in SimulatedCommands
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0);
+
+        // radio technology changed
+        attr = new ImsRegistrationAttributes.Builder(REGISTRATION_TECH_IWLAN).build();
+        registrationCallback.onRegistered(attr);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_REGISTERED
+                && regInfo[1] == REGISTRATION_TECH_IWLAN
+                && regInfo[3] == IMS_MMTEL_CAPABILITY_VOICE);
+
+        // reset the registration info saved in the SimulatedCommands
+        mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0);
+
+        // duplicated notification with the same radio technology
+        attr = new ImsRegistrationAttributes.Builder(REGISTRATION_TECH_IWLAN).build();
+        registrationCallback.onRegistered(attr);
+
+        // verify that there is no change in SimulatedCommands
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0);
+
+        // radio technology changed
+        attr = new ImsRegistrationAttributes.Builder(REGISTRATION_TECH_3G).build();
+        registrationCallback.onRegistered(attr);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_REGISTERED
+                && regInfo[1] == REGISTRATION_TECH_3G
+                && regInfo[3] == IMS_MMTEL_CAPABILITY_VOICE);
+
+        // reset the registration info saved in the SimulatedCommands
+        mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0);
+
+        // duplicated notification with the same radio technology
+        attr = new ImsRegistrationAttributes.Builder(REGISTRATION_TECH_3G).build();
+        registrationCallback.onRegistered(attr);
+
+        // verify that there is no change in SimulatedCommands
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0);
+    }
+
+    /**
+     * Verifies that valid capabilities is passed to RIL
+     * when IMS registration state changes to registered.
+     */
+    @Test
+    @SmallTest
+    public void testUpdateImsRegistrationInfoCapabilities() {
+        mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null);
+
+        int[] regInfo = mSimulatedCommands.getImsRegistrationInfo();
+        assertNotNull(regInfo);
+        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0);
+
+        RegistrationManager.RegistrationCallback registrationCallback =
+                mImsPhoneUT.getImsMmTelRegistrationCallback();
+
+        ImsRegistrationAttributes attr = new ImsRegistrationAttributes.Builder(
+                REGISTRATION_TECH_LTE).build();
+        registrationCallback.onRegistered(attr);
+        mImsPhoneUT.updateImsRegistrationInfo(IMS_MMTEL_CAPABILITY_VOICE);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_REGISTERED
+                && regInfo[1] == REGISTRATION_TECH_LTE
+                && regInfo[3] == IMS_MMTEL_CAPABILITY_VOICE);
+
+        // reset the registration info saved in the SimulatedCommands
+        mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0);
+
+        // duplicated notification with the same capability
+        mImsPhoneUT.updateImsRegistrationInfo(IMS_MMTEL_CAPABILITY_VOICE);
+
+        // verify that there is no change in SimulatedCommands
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0);
+
+        // capability changed
+        mImsPhoneUT.updateImsRegistrationInfo(IMS_MMTEL_CAPABILITY_VIDEO);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_REGISTERED
+                && regInfo[1] == REGISTRATION_TECH_LTE
+                && regInfo[3] == IMS_MMTEL_CAPABILITY_VIDEO);
+
+        // reset the registration info saved in the SimulatedCommands
+        mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0);
+
+        // duplicated notification with the same capability
+        mImsPhoneUT.updateImsRegistrationInfo(IMS_MMTEL_CAPABILITY_VIDEO);
+
+        // verify that there is no change in SimulatedCommands
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0);
+
+        // capability changed
+        mImsPhoneUT.updateImsRegistrationInfo(IMS_MMTEL_CAPABILITY_SMS);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_REGISTERED
+                && regInfo[1] == REGISTRATION_TECH_LTE
+                && regInfo[3] == IMS_MMTEL_CAPABILITY_SMS);
+
+        // reset the registration info saved in the SimulatedCommands
+        mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0);
+
+        // duplicated notification with the same capability
+        mImsPhoneUT.updateImsRegistrationInfo(IMS_MMTEL_CAPABILITY_SMS);
+
+        // verify that there is no change in SimulatedCommands
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0);
+
+        // capability changed, but no capability
+        mImsPhoneUT.updateImsRegistrationInfo(IMS_MMTEL_CAPABILITY_SMS);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        // verify that there is no change in SimulatedCommands.
+        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[3] == 0);
+    }
+
+    /**
+     * Verifies that valid state and reason is passed to RIL
+     * when IMS registration state changes to unregistered.
+     */
+    @Test
+    @SmallTest
+    public void testUpdateImsRegistrationInfo() {
+        mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null);
+
+        int[] regInfo = mSimulatedCommands.getImsRegistrationInfo();
+        assertNotNull(regInfo);
+        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[2] == 0);
+
+        RegistrationManager.RegistrationCallback registrationCallback =
+                mImsPhoneUT.getImsMmTelRegistrationCallback();
+
+        ImsReasonInfo reasonInfo = new ImsReasonInfo(ImsReasonInfo.CODE_REGISTRATION_ERROR,
+                ImsReasonInfo.CODE_UNSPECIFIED, "");
+
+        // unregistered with fatal error
+        registrationCallback.onUnregistered(reasonInfo,
+                SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK, REGISTRATION_TECH_NR);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED
+                && regInfo[1] == REGISTRATION_TECH_NR
+                && regInfo[2] == SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK);
+
+        // reset the registration info saved in the SimulatedCommands
+        mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[2] == 0);
+
+        // unregistered with fatal error but rat changed
+        registrationCallback.onUnregistered(reasonInfo,
+                SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK, REGISTRATION_TECH_LTE);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED
+                && regInfo[1] == REGISTRATION_TECH_LTE
+                && regInfo[2] == SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK);
+
+        // reset the registration info saved in the SimulatedCommands
+        mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[2] == 0);
+
+        // duplicated notification with the same suggested action
+        registrationCallback.onUnregistered(reasonInfo,
+                SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK, REGISTRATION_TECH_LTE);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        // verify that there is no update in the SimulatedCommands
+        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[2] == 0);
+
+        // unregistered with repeated error
+        registrationCallback.onUnregistered(reasonInfo,
+                SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT,
+                REGISTRATION_TECH_LTE);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED
+                && regInfo[1] == REGISTRATION_TECH_LTE
+                && regInfo[2] == SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT);
+
+        // reset the registration info saved in the SimulatedCommands
+        mSimulatedCommands.updateImsRegistrationInfo(0, 0, 0, 0, null);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[2] == 0);
+
+        // duplicated notification with the same suggested action
+        registrationCallback.onUnregistered(reasonInfo,
+                SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK_WITH_TIMEOUT,
+                REGISTRATION_TECH_LTE);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        // verify that there is no update in the SimulatedCommands
+        assertTrue(regInfo[0] == 0 && regInfo[1] == 0 && regInfo[2] == 0);
+
+        // unregistered with temporary error
+        registrationCallback.onUnregistered(reasonInfo,
+                SUGGESTED_ACTION_NONE, REGISTRATION_TECH_LTE);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED
+                && regInfo[1] == REGISTRATION_TECH_LTE
+                && regInfo[2] == SUGGESTED_ACTION_NONE);
+
+        // verifies that reason codes except ImsReasonInfo.CODE_REGISTRATION_ERROR are discarded.
+        reasonInfo = new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_NETWORK_NO_SERVICE,
+                ImsReasonInfo.CODE_UNSPECIFIED, "");
+        registrationCallback.onUnregistered(reasonInfo,
+                SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK, REGISTRATION_TECH_NR);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED
+                && regInfo[1] == REGISTRATION_TECH_NR
+                && regInfo[2] == SUGGESTED_ACTION_NONE);
+
+        // change the registration info saved in the SimulatedCommands
+        mSimulatedCommands.updateImsRegistrationInfo(1, 1, 1, 1, null);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        assertTrue(regInfo[0] == 1 && regInfo[1] == 1 && regInfo[2] == 1);
+
+        // duplicated notification with the same suggested action
+        registrationCallback.onUnregistered(reasonInfo,
+                SUGGESTED_ACTION_NONE, REGISTRATION_TECH_NR);
+        regInfo = mSimulatedCommands.getImsRegistrationInfo();
+
+        // verify that there is no update in the SimulatedCommands
+        assertTrue(regInfo[0] == 1 && regInfo[1] == 1 && regInfo[2] == 1);
+    }
+
+    @Test
+    @SmallTest
+    public void testImsDialArgsBuilderFromForAlternateService() {
+        ImsPhone.ImsDialArgs dialArgs = new ImsPhone.ImsDialArgs.Builder()
+                .setIsEmergency(true)
+                .setEccCategory(2)
+                .build();
+
+        ImsPhone.ImsDialArgs copiedDialArgs =
+                ImsPhone.ImsDialArgs.Builder.from(dialArgs).build();
+
+        assertTrue(copiedDialArgs.isEmergency);
+        assertEquals(2, copiedDialArgs.eccCategory);
+    }
+
     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 cce0646..93fbfdc 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsRegistrationCallbackHelperTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsRegistrationCallbackHelperTest.java
@@ -16,10 +16,16 @@
 
 package com.android.internal.telephony.imsphone;
 
+import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_NONE;
+import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK;
+import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_LTE;
+import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NONE;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -27,8 +33,10 @@
 import android.net.Uri;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsRegistrationAttributes;
 import android.telephony.ims.RegistrationManager;
 import android.telephony.ims.RegistrationManager.RegistrationCallback;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.internal.telephony.TelephonyTest;
@@ -121,11 +129,13 @@
 
         // When onRegistered is called, the registration state should be
         // REGISTRATION_STATE_REGISTERED
-        callback.onRegistered(AccessNetworkType.IWLAN);
+        ImsRegistrationAttributes attr = new ImsRegistrationAttributes.Builder(
+                ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN).build();
+        callback.onRegistered(attr);
 
         assertEquals(RegistrationManager.REGISTRATION_STATE_REGISTERED,
                 mRegistrationCallbackHelper.getImsRegistrationState());
-        verify(mMockRegistrationUpdate).handleImsRegistered(anyInt());
+        verify(mMockRegistrationUpdate).handleImsRegistered(attr);
     }
 
     @Test
@@ -158,7 +168,25 @@
         // The registration state should be REGISTRATION_STATE_NOT_REGISTERED
         assertEquals(RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED,
                 mRegistrationCallbackHelper.getImsRegistrationState());
-        verify(mMockRegistrationUpdate).handleImsUnregistered(reasonInfo);
+        verify(mMockRegistrationUpdate).handleImsUnregistered(eq(reasonInfo),
+                eq(SUGGESTED_ACTION_NONE), eq(REGISTRATION_TECH_NONE));
+    }
+
+    @Test
+    @SmallTest
+    public void testImsUnRegisteredWithSuggestedAction() {
+        // Verify the RegistrationCallback should not be null
+        RegistrationCallback callback = mRegistrationCallbackHelper.getCallback();
+        assertNotNull(callback);
+
+        ImsReasonInfo reasonInfo = new ImsReasonInfo(ImsReasonInfo.CODE_REGISTRATION_ERROR, 0);
+        callback.onUnregistered(reasonInfo, SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK,
+                REGISTRATION_TECH_LTE);
+
+        assertEquals(RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED,
+                mRegistrationCallbackHelper.getImsRegistrationState());
+        verify(mMockRegistrationUpdate).handleImsUnregistered(eq(reasonInfo),
+                eq(SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK), eq(REGISTRATION_TECH_LTE));
     }
 
     @Test
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 a4e2574..d4e1b86 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/MetricsCollectorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/MetricsCollectorTest.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_DATA_SERVICE_SWITCH;
 import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_SERVICE_STATE;
+import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SHORT_CODE_SMS;
 import static com.android.internal.telephony.TelephonyStatsLog.SIM_SLOT_STATE;
 import static com.android.internal.telephony.TelephonyStatsLog.SUPPORTED_RADIO_ACCESS_FAMILY;
 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_RAT_USAGE;
@@ -43,6 +44,7 @@
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState;
+import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingShortCodeSms;
 import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallRatUsage;
 import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession;
 import com.android.internal.telephony.uicc.IccCardStatus.CardState;
@@ -102,7 +104,7 @@
         mActivePort = mock(UiccPort.class);
         mServiceStateStats = mock(ServiceStateStats.class);
         mMetricsCollector =
-                new MetricsCollector(mContext, mPersistAtomsStorage);
+                new MetricsCollector(mContext, mPersistAtomsStorage, mDeviceStateHelper);
         doReturn(mSST).when(mSecondPhone).getServiceStateTracker();
         doReturn(mServiceStateStats).when(mSST).getServiceStateStats();
     }
@@ -412,4 +414,45 @@
         assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
         // TODO(b/153196254): verify atom contents
     }
+
+    @Test
+    public void onPullAtom_outgoingShortCodeSms_empty() {
+        doReturn(new OutgoingShortCodeSms[0]).when(mPersistAtomsStorage)
+                .getOutgoingShortCodeSms(anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(OUTGOING_SHORT_CODE_SMS, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(0);
+        assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
+    }
+
+    @Test
+    public void onPullAtom_outgoingShortCodeSms_tooFrequent() {
+        doReturn(null).when(mPersistAtomsStorage).getOutgoingShortCodeSms(anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(OUTGOING_SHORT_CODE_SMS, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(0);
+        assertThat(result).isEqualTo(StatsManager.PULL_SKIP);
+        verify(mPersistAtomsStorage, times(1))
+                .getOutgoingShortCodeSms(eq(MIN_COOLDOWN_MILLIS));
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    public void onPullAtom_outgoingShortCodeSms_multipleSms() {
+        OutgoingShortCodeSms outgoingShortCodeSms = new OutgoingShortCodeSms();
+        doReturn(new OutgoingShortCodeSms[] {outgoingShortCodeSms, outgoingShortCodeSms,
+                outgoingShortCodeSms, outgoingShortCodeSms})
+                .when(mPersistAtomsStorage)
+                .getOutgoingShortCodeSms(anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(OUTGOING_SHORT_CODE_SMS, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(4);
+        assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
+    }
 }
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 10151b8..dc9be1c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/PerSimStatusTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/PerSimStatusTest.java
@@ -43,7 +43,6 @@
 import com.android.internal.telephony.IccCard;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
-import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
@@ -84,26 +83,16 @@
         doReturn(1).when(mPhone).getSubId();
         doReturn(100).when(mPhone).getCarrierId();
         doReturn("6506953210")
-                .when(mSubscriptionController)
-                .getPhoneNumber(1, PHONE_NUMBER_SOURCE_UICC, null, null);
-        doReturn("6506953210")
                 .when(mSubscriptionManagerService)
                 .getPhoneNumber(1, PHONE_NUMBER_SOURCE_UICC, null, null);
         doReturn("")
-                .when(mSubscriptionController)
-                .getPhoneNumber(1, PHONE_NUMBER_SOURCE_CARRIER, null, null);
-        doReturn("")
                 .when(mSubscriptionManagerService)
                 .getPhoneNumber(1, PHONE_NUMBER_SOURCE_CARRIER, null, null);
         doReturn("+16506953210")
-                .when(mSubscriptionController)
-                .getPhoneNumber(1, PHONE_NUMBER_SOURCE_IMS, null, null);
-        doReturn("+16506953210")
                 .when(mSubscriptionManagerService)
                 .getPhoneNumber(1, PHONE_NUMBER_SOURCE_IMS, null, null);
         SubscriptionInfo subscriptionInfo1 = mock(SubscriptionInfo.class);
         doReturn("us").when(subscriptionInfo1).getCountryIso();
-        doReturn(subscriptionInfo1).when(mSubscriptionController).getSubscriptionInfo(1);
         doReturn(new SubscriptionInfoInternal.Builder().setId(1).setSimSlotIndex(0)
                 .setCountryIso("us").build()).when(mSubscriptionManagerService)
                 .getSubscriptionInfoInternal(1);
@@ -133,32 +122,23 @@
         doReturn(UiccSlot.VOLTAGE_CLASS_A).when(uiccSlot1).getMinimumVoltageClass();
         doReturn(uiccSlot1).when(mUiccController).getUiccSlotForPhone(0);
         doReturn(NETWORK_TYPE_BITMASK_GSM).when(mPersistAtomsStorage).getUnmeteredNetworks(0, 100);
+        doReturn(false).when(mTelephonyManager).isVoNrEnabled();
         // phone 1 setup
         doReturn(mContext).when(mSecondPhone).getContext();
         doReturn(1).when(mSecondPhone).getPhoneId();
         doReturn(2).when(mSecondPhone).getSubId();
         doReturn(101).when(mSecondPhone).getCarrierId();
         doReturn("0123")
-                .when(mSubscriptionController)
-                .getPhoneNumber(2, PHONE_NUMBER_SOURCE_UICC, null, null);
-        doReturn("0123")
                 .when(mSubscriptionManagerService)
                 .getPhoneNumber(2, PHONE_NUMBER_SOURCE_UICC, null, null);
         doReturn("16506950123")
-                .when(mSubscriptionController)
-                .getPhoneNumber(2, PHONE_NUMBER_SOURCE_CARRIER, null, null);
-        doReturn("16506950123")
                 .when(mSubscriptionManagerService)
                 .getPhoneNumber(2, PHONE_NUMBER_SOURCE_CARRIER, null, null);
         doReturn("+16506950123")
-                .when(mSubscriptionController)
-                .getPhoneNumber(2, PHONE_NUMBER_SOURCE_IMS, null, null);
-        doReturn("+16506950123")
                 .when(mSubscriptionManagerService)
                 .getPhoneNumber(2, PHONE_NUMBER_SOURCE_IMS, null, null);
         SubscriptionInfo subscriptionInfo2 = mock(SubscriptionInfo.class);
         doReturn("us").when(subscriptionInfo2).getCountryIso();
-        doReturn(subscriptionInfo2).when(mSubscriptionController).getSubscriptionInfo(2);
         doReturn(new SubscriptionInfoInternal.Builder().setId(2).setSimSlotIndex(1)
                 .setCountryIso("us").build()).when(mSubscriptionManagerService)
                 .getSubscriptionInfoInternal(2);
@@ -210,6 +190,7 @@
                 PER_SIM_STATUS__SIM_VOLTAGE_CLASS__VOLTAGE_CLASS_A,
                 perSimStatus1.minimumVoltageClass);
         assertEquals(NETWORK_TYPE_BITMASK_GSM, perSimStatus1.unmeteredNetworks);
+        assertEquals(false, perSimStatus1.vonrEnabled);
         assertEquals(101, perSimStatus2.carrierId);
         assertEquals(1, perSimStatus2.phoneNumberSourceUicc);
         assertEquals(2, perSimStatus2.phoneNumberSourceCarrier);
@@ -228,12 +209,12 @@
                 PER_SIM_STATUS__SIM_VOLTAGE_CLASS__VOLTAGE_CLASS_B,
                 perSimStatus2.minimumVoltageClass);
         assertEquals(NETWORK_TYPE_BITMASK_GSM, perSimStatus2.unmeteredNetworks);
+        assertEquals(false, perSimStatus2.vonrEnabled);
     }
 
     @Test
     @SmallTest
-    public void onPullAtom_perSimStatus_noSubscriptionController() throws Exception {
-        replaceInstance(SubscriptionController.class, "sInstance", null, null);
+    public void onPullAtom_perSimStatus_noSubscriptionManagerService() throws Exception {
         replaceInstance(SubscriptionManagerService.class, "sInstance", null, null);
 
         PerSimStatus perSimStatus = PerSimStatus.getCurrentState(mPhone);
@@ -248,26 +229,16 @@
         doReturn(1).when(mPhone).getSubId();
         doReturn(100).when(mPhone).getCarrierId();
         doReturn("6506953210")
-                .when(mSubscriptionController)
-                .getPhoneNumber(1, PHONE_NUMBER_SOURCE_UICC, null, null);
-        doReturn("6506953210")
                 .when(mSubscriptionManagerService)
                 .getPhoneNumber(1, PHONE_NUMBER_SOURCE_UICC, null, null);
         doReturn("")
-                .when(mSubscriptionController)
-                .getPhoneNumber(1, PHONE_NUMBER_SOURCE_CARRIER, null, null);
-        doReturn("")
                 .when(mSubscriptionManagerService)
                 .getPhoneNumber(1, PHONE_NUMBER_SOURCE_CARRIER, null, null);
         doReturn("+16506953210")
-                .when(mSubscriptionController)
-                .getPhoneNumber(1, PHONE_NUMBER_SOURCE_IMS, null, null);
-        doReturn("+16506953210")
                 .when(mSubscriptionManagerService)
                 .getPhoneNumber(1, PHONE_NUMBER_SOURCE_IMS, null, null);
         SubscriptionInfo subscriptionInfo = mock(SubscriptionInfo.class);
         doReturn("us").when(subscriptionInfo).getCountryIso();
-        doReturn(subscriptionInfo).when(mSubscriptionController).getSubscriptionInfo(1);
         doReturn(new SubscriptionInfoInternal.Builder().setId(1).setSimSlotIndex(0)
                 .setCountryIso("us").build()).when(mSubscriptionManagerService)
                 .getSubscriptionInfoInternal(1);
@@ -286,6 +257,7 @@
         doReturn(UiccSlot.VOLTAGE_CLASS_A).when(uiccSlot1).getMinimumVoltageClass();
         doReturn(uiccSlot1).when(mUiccController).getUiccSlotForPhone(0);
         doReturn(NETWORK_TYPE_BITMASK_GSM).when(mPersistAtomsStorage).getUnmeteredNetworks(0, 100);
+        doReturn(true).when(mTelephonyManager).isVoNrEnabled();
 
         PerSimStatus perSimStatus = PerSimStatus.getCurrentState(mPhone);
 
@@ -306,6 +278,7 @@
                 PER_SIM_STATUS__SIM_VOLTAGE_CLASS__VOLTAGE_CLASS_A,
                 perSimStatus.minimumVoltageClass);
         assertEquals(NETWORK_TYPE_BITMASK_GSM, perSimStatus.unmeteredNetworks);
+        assertEquals(true, perSimStatus.vonrEnabled);
     }
 
     @Test
@@ -315,26 +288,16 @@
         doReturn(1).when(mPhone).getSubId();
         doReturn(100).when(mPhone).getCarrierId();
         doReturn("6506953210")
-                .when(mSubscriptionController)
-                .getPhoneNumber(1, PHONE_NUMBER_SOURCE_UICC, null, null);
-        doReturn("6506953210")
                 .when(mSubscriptionManagerService)
                 .getPhoneNumber(1, PHONE_NUMBER_SOURCE_UICC, null, null);
         doReturn("")
-                .when(mSubscriptionController)
-                .getPhoneNumber(1, PHONE_NUMBER_SOURCE_CARRIER, null, null);
-        doReturn("")
                 .when(mSubscriptionManagerService)
                 .getPhoneNumber(1, PHONE_NUMBER_SOURCE_CARRIER, null, null);
         doReturn("+16506953210")
-                .when(mSubscriptionController)
-                .getPhoneNumber(1, PHONE_NUMBER_SOURCE_IMS, null, null);
-        doReturn("+16506953210")
                 .when(mSubscriptionManagerService)
                 .getPhoneNumber(1, PHONE_NUMBER_SOURCE_IMS, null, null);
         SubscriptionInfo subscriptionInfo = mock(SubscriptionInfo.class);
         doReturn("us").when(subscriptionInfo).getCountryIso();
-        doReturn(subscriptionInfo).when(mSubscriptionController).getSubscriptionInfo(1);
         doReturn(new SubscriptionInfoInternal.Builder().setId(1).setSimSlotIndex(0)
                 .setCountryIso("us").build()).when(mSubscriptionManagerService)
                 .getSubscriptionInfoInternal(1);
@@ -355,6 +318,7 @@
         doReturn(UiccSlot.VOLTAGE_CLASS_A).when(uiccSlot1).getMinimumVoltageClass();
         doReturn(uiccSlot1).when(mUiccController).getUiccSlotForPhone(0);
         doReturn(NETWORK_TYPE_BITMASK_GSM).when(mPersistAtomsStorage).getUnmeteredNetworks(0, 100);
+        doReturn(true).when(mTelephonyManager).isVoNrEnabled();
 
         PerSimStatus perSimStatus = PerSimStatus.getCurrentState(mPhone);
 
@@ -375,6 +339,7 @@
                 PER_SIM_STATUS__SIM_VOLTAGE_CLASS__VOLTAGE_CLASS_A,
                 perSimStatus.minimumVoltageClass);
         assertEquals(NETWORK_TYPE_BITMASK_GSM, perSimStatus.unmeteredNetworks);
+        assertEquals(true, perSimStatus.vonrEnabled);
     }
 
     @Test
@@ -384,26 +349,16 @@
         doReturn(1).when(mPhone).getSubId();
         doReturn(100).when(mPhone).getCarrierId();
         doReturn("6506953210")
-                .when(mSubscriptionController)
-                .getPhoneNumber(1, PHONE_NUMBER_SOURCE_UICC, null, null);
-        doReturn("6506953210")
                 .when(mSubscriptionManagerService)
                 .getPhoneNumber(1, PHONE_NUMBER_SOURCE_UICC, null, null);
         doReturn("")
-                .when(mSubscriptionController)
-                .getPhoneNumber(1, PHONE_NUMBER_SOURCE_CARRIER, null, null);
-        doReturn("")
                 .when(mSubscriptionManagerService)
                 .getPhoneNumber(1, PHONE_NUMBER_SOURCE_CARRIER, null, null);
         doReturn("+16506953210")
-                .when(mSubscriptionController)
-                .getPhoneNumber(1, PHONE_NUMBER_SOURCE_IMS, null, null);
-        doReturn("+16506953210")
                 .when(mSubscriptionManagerService)
                 .getPhoneNumber(1, PHONE_NUMBER_SOURCE_IMS, null, null);
         SubscriptionInfo subscriptionInfo = mock(SubscriptionInfo.class);
         doReturn("us").when(subscriptionInfo).getCountryIso();
-        doReturn(subscriptionInfo).when(mSubscriptionController).getSubscriptionInfo(1);
         doReturn(new SubscriptionInfoInternal.Builder().setId(1).setSimSlotIndex(0)
                 .setCountryIso("us").build()).when(mSubscriptionManagerService)
                 .getSubscriptionInfoInternal(1);
@@ -420,6 +375,7 @@
         doReturn(iccCard).when(mPhone).getIccCard();
         doReturn(null).when(mUiccController).getUiccSlotForPhone(0);
         doReturn(NETWORK_TYPE_BITMASK_GSM).when(mPersistAtomsStorage).getUnmeteredNetworks(0, 100);
+        doReturn(true).when(mTelephonyManager).isVoNrEnabled();
 
         PerSimStatus perSimStatus = PerSimStatus.getCurrentState(mPhone);
 
@@ -440,5 +396,6 @@
                 PER_SIM_STATUS__SIM_VOLTAGE_CLASS__VOLTAGE_CLASS_UNKNOWN,
                 perSimStatus.minimumVoltageClass);
         assertEquals(NETWORK_TYPE_BITMASK_GSM, perSimStatus.unmeteredNetworks);
+        assertEquals(true, perSimStatus.vonrEnabled);
     }
 }
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 e46b822..3307813 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
@@ -49,6 +49,7 @@
 import android.content.Context;
 import android.os.Build;
 import android.telephony.DisconnectCause;
+import android.telephony.SatelliteProtoEnums;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.telephony.TelephonyProtoEnums;
@@ -68,10 +69,17 @@
 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationServiceDescStats;
 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationStats;
 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationTermination;
+import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingShortCodeSms;
 import com.android.internal.telephony.nano.PersistAtomsProto.PersistAtoms;
 import com.android.internal.telephony.nano.PersistAtomsProto.PresenceNotifyEvent;
 import com.android.internal.telephony.nano.PersistAtomsProto.RcsAcsProvisioningStats;
 import com.android.internal.telephony.nano.PersistAtomsProto.RcsClientProvisioningStats;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteController;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteIncomingDatagram;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteOutgoingDatagram;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteProvision;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteSession;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteSosMessageRecommender;
 import com.android.internal.telephony.nano.PersistAtomsProto.SipDelegateStats;
 import com.android.internal.telephony.nano.PersistAtomsProto.SipMessageResponse;
 import com.android.internal.telephony.nano.PersistAtomsProto.SipTransportFeatureTagStats;
@@ -140,7 +148,7 @@
     private CellularDataServiceSwitch mServiceSwitch1Proto;
     private CellularDataServiceSwitch mServiceSwitch2Proto;
 
-    // service states for slot 0 and 1
+    // Service states for slot 0 and 1
     private CellularServiceState mServiceState1Proto;
     private CellularServiceState mServiceState2Proto;
     private CellularServiceState mServiceState3Proto;
@@ -224,6 +232,34 @@
     private SipTransportSession mSipTransportSession2;
     private SipTransportSession[] mSipTransportSession;
 
+    private OutgoingShortCodeSms mOutgoingShortCodeSms1;
+    private OutgoingShortCodeSms mOutgoingShortCodeSms2;
+    private OutgoingShortCodeSms[] mOutgoingShortCodeSms;
+
+    private SatelliteController mSatelliteController1;
+    private SatelliteController mSatelliteController2;
+    private SatelliteController[] mSatelliteControllers;
+
+    private SatelliteSession mSatelliteSession1;
+    private SatelliteSession mSatelliteSession2;
+    private SatelliteSession[] mSatelliteSessions;
+
+    private SatelliteIncomingDatagram mSatelliteIncomingDatagram1;
+    private SatelliteIncomingDatagram mSatelliteIncomingDatagram2;
+    private SatelliteIncomingDatagram[] mSatelliteIncomingDatagrams;
+
+    private SatelliteOutgoingDatagram mSatelliteOutgoingDatagram1;
+    private SatelliteOutgoingDatagram mSatelliteOutgoingDatagram2;
+    private SatelliteOutgoingDatagram[] mSatelliteOutgoingDatagrams;
+
+    private SatelliteProvision mSatelliteProvision1;
+    private SatelliteProvision mSatelliteProvision2;
+    private SatelliteProvision[] mSatelliteProvisions;
+
+    private SatelliteSosMessageRecommender mSatelliteSosMessageRecommender1;
+    private SatelliteSosMessageRecommender mSatelliteSosMessageRecommender2;
+    private SatelliteSosMessageRecommender[] mSatelliteSosMessageRecommenders;
+
     private void makeTestData() {
         // MO call with SRVCC (LTE to UMTS)
         mCall1Proto = new VoiceCallSession();
@@ -859,6 +895,158 @@
 
         mSipTransportSession =
                 new SipTransportSession[] {mSipTransportSession1, mSipTransportSession2};
+
+        mOutgoingShortCodeSms1 = new OutgoingShortCodeSms();
+        mOutgoingShortCodeSms1.category = 1;
+        mOutgoingShortCodeSms1.xmlVersion = 30;
+        mOutgoingShortCodeSms1.shortCodeSmsCount = 1;
+
+        mOutgoingShortCodeSms2 = new OutgoingShortCodeSms();
+        mOutgoingShortCodeSms2.category = 2;
+        mOutgoingShortCodeSms2.xmlVersion  = 31;
+        mOutgoingShortCodeSms2.shortCodeSmsCount = 1;
+
+        mOutgoingShortCodeSms = new OutgoingShortCodeSms[] {mOutgoingShortCodeSms1,
+                mOutgoingShortCodeSms2};
+
+        generateTestSatelliteData();
+    }
+
+    private void generateTestSatelliteData() {
+        mSatelliteController1 = new SatelliteController();
+        mSatelliteController1.countOfSatelliteServiceEnablementsSuccess = 2;
+        mSatelliteController1.countOfSatelliteServiceEnablementsFail = 0;
+        mSatelliteController1.countOfOutgoingDatagramSuccess = 8;
+        mSatelliteController1.countOfOutgoingDatagramFail = 9;
+        mSatelliteController1.countOfIncomingDatagramSuccess = 10;
+        mSatelliteController1.countOfIncomingDatagramFail = 11;
+        mSatelliteController1.countOfDatagramTypeSosSmsSuccess = 5;
+        mSatelliteController1.countOfDatagramTypeSosSmsFail = 5;
+        mSatelliteController1.countOfDatagramTypeLocationSharingSuccess = 6;
+        mSatelliteController1.countOfDatagramTypeLocationSharingFail = 6;
+        mSatelliteController1.countOfProvisionSuccess = 3;
+        mSatelliteController1.countOfProvisionFail = 4;
+        mSatelliteController1.countOfDeprovisionSuccess = 5;
+        mSatelliteController1.countOfDeprovisionFail = 6;
+        mSatelliteController1.totalServiceUptimeSec = 60 * 60 * 24 * 7;
+        mSatelliteController1.totalBatteryConsumptionPercent = 7;
+        mSatelliteController1.totalBatteryChargedTimeSec = 60 * 60 * 3 * 1;
+
+        mSatelliteController2 = new SatelliteController();
+        mSatelliteController2.countOfSatelliteServiceEnablementsSuccess = 2 + 1;
+        mSatelliteController2.countOfSatelliteServiceEnablementsFail = 0 + 1;
+        mSatelliteController2.countOfOutgoingDatagramSuccess = 8 + 1;
+        mSatelliteController2.countOfOutgoingDatagramFail = 9 + 1;
+        mSatelliteController2.countOfIncomingDatagramSuccess = 10 + 1;
+        mSatelliteController2.countOfIncomingDatagramFail = 11 + 1;
+        mSatelliteController2.countOfDatagramTypeSosSmsSuccess = 5 + 1;
+        mSatelliteController2.countOfDatagramTypeSosSmsFail = 5 + 1;
+        mSatelliteController2.countOfDatagramTypeLocationSharingSuccess = 6 + 1;
+        mSatelliteController2.countOfDatagramTypeLocationSharingFail = 6 + 1;
+        mSatelliteController2.countOfProvisionSuccess = 13;
+        mSatelliteController2.countOfProvisionFail = 14;
+        mSatelliteController2.countOfDeprovisionSuccess = 15;
+        mSatelliteController2.countOfDeprovisionFail = 16;
+        mSatelliteController2.totalServiceUptimeSec = 60 * 60 * 12;
+        mSatelliteController2.totalBatteryConsumptionPercent = 14;
+        mSatelliteController1.totalBatteryChargedTimeSec = 60 * 60 * 3;
+
+        // SatelliteController atom has one data point
+        mSatelliteControllers =
+                new SatelliteController[] {
+                        mSatelliteController1
+                };
+
+        mSatelliteSession1 = new SatelliteSession();
+        mSatelliteSession1.satelliteServiceInitializationResult =
+                SatelliteProtoEnums.SATELLITE_ERROR_NONE;
+        mSatelliteSession1.satelliteTechnology =
+                SatelliteProtoEnums.NT_RADIO_TECHNOLOGY_PROPRIETARY;
+        mSatelliteSession1.count = 1;
+
+        mSatelliteSession2 = new SatelliteSession();
+        mSatelliteSession2.satelliteServiceInitializationResult =
+                SatelliteProtoEnums.SATELLITE_MODEM_ERROR;
+        mSatelliteSession2.satelliteTechnology =
+                SatelliteProtoEnums.NT_RADIO_TECHNOLOGY_NB_IOT_NTN;
+        mSatelliteSession2.count = 1;
+
+        mSatelliteSessions =
+                new SatelliteSession[] {
+                        mSatelliteSession1, mSatelliteSession2
+                };
+
+        mSatelliteIncomingDatagram1 = new SatelliteIncomingDatagram();
+        mSatelliteIncomingDatagram1.resultCode = SatelliteProtoEnums.SATELLITE_ERROR_NONE;
+        mSatelliteIncomingDatagram1.datagramSizeBytes = 1 * 1024;
+        mSatelliteIncomingDatagram1.datagramTransferTimeMillis = 3 * 1000;
+
+        mSatelliteIncomingDatagram2 = new SatelliteIncomingDatagram();
+        mSatelliteIncomingDatagram2.resultCode = SatelliteProtoEnums.SATELLITE_MODEM_ERROR;
+        mSatelliteIncomingDatagram2.datagramSizeBytes = 512;
+        mSatelliteIncomingDatagram2.datagramTransferTimeMillis = 1 * 1000;
+
+        mSatelliteIncomingDatagrams =
+                new SatelliteIncomingDatagram[] {
+                        mSatelliteIncomingDatagram1, mSatelliteIncomingDatagram2
+                };
+
+        mSatelliteOutgoingDatagram1 = new SatelliteOutgoingDatagram();
+        mSatelliteOutgoingDatagram1.datagramType =
+                SatelliteProtoEnums.DATAGRAM_TYPE_LOCATION_SHARING;
+        mSatelliteOutgoingDatagram1.resultCode = SatelliteProtoEnums.SATELLITE_ERROR_NONE;
+        mSatelliteOutgoingDatagram1.datagramSizeBytes = 1 * 1024;
+        mSatelliteOutgoingDatagram1.datagramTransferTimeMillis = 3 * 1000;
+
+        mSatelliteOutgoingDatagram2 = new SatelliteOutgoingDatagram();
+        mSatelliteOutgoingDatagram2.datagramType =
+                SatelliteProtoEnums.DATAGRAM_TYPE_SOS_MESSAGE;
+        mSatelliteOutgoingDatagram2.resultCode = SatelliteProtoEnums.SATELLITE_MODEM_ERROR;
+        mSatelliteOutgoingDatagram2.datagramSizeBytes = 512;
+        mSatelliteOutgoingDatagram2.datagramTransferTimeMillis = 1 * 1000;
+
+        mSatelliteOutgoingDatagrams =
+                new SatelliteOutgoingDatagram[] {
+                        mSatelliteOutgoingDatagram1, mSatelliteOutgoingDatagram2
+                };
+
+        mSatelliteProvision1 = new SatelliteProvision();
+        mSatelliteProvision1.resultCode = SatelliteProtoEnums.SATELLITE_ERROR_NONE;
+        mSatelliteProvision1.provisioningTimeSec = 3 * 60;
+        mSatelliteProvision1.isProvisionRequest = true;
+        mSatelliteProvision1.isCanceled = false;
+
+        mSatelliteProvision2 = new SatelliteProvision();
+        mSatelliteProvision2.resultCode = SatelliteProtoEnums.SATELLITE_SERVICE_NOT_PROVISIONED;
+        mSatelliteProvision2.provisioningTimeSec = 0;
+        mSatelliteProvision2.isProvisionRequest = false;
+        mSatelliteProvision2.isCanceled = true;
+
+        mSatelliteProvisions =
+                new SatelliteProvision[] {
+                        mSatelliteProvision1, mSatelliteProvision2
+                };
+
+        mSatelliteSosMessageRecommender1 = new SatelliteSosMessageRecommender();
+        mSatelliteSosMessageRecommender1.isDisplaySosMessageSent = true;
+        mSatelliteSosMessageRecommender1.countOfTimerStarted = 5;
+        mSatelliteSosMessageRecommender1.isImsRegistered = false;
+        mSatelliteSosMessageRecommender1.cellularServiceState =
+                TelephonyProtoEnums.SERVICE_STATE_OUT_OF_SERVICE;
+        mSatelliteSosMessageRecommender1.count = 1;
+
+        mSatelliteSosMessageRecommender2 = new SatelliteSosMessageRecommender();
+        mSatelliteSosMessageRecommender2.isDisplaySosMessageSent = false;
+        mSatelliteSosMessageRecommender2.countOfTimerStarted = 3;
+        mSatelliteSosMessageRecommender2.isImsRegistered = true;
+        mSatelliteSosMessageRecommender2.cellularServiceState =
+                TelephonyProtoEnums.SERVICE_STATE_POWER_OFF;
+        mSatelliteSosMessageRecommender2.count = 1;
+
+        mSatelliteSosMessageRecommenders =
+                new SatelliteSosMessageRecommender[] {
+                        mSatelliteSosMessageRecommender1, mSatelliteSosMessageRecommender2
+                };
     }
 
     private static class TestablePersistAtomsStorage extends PersistAtomsStorage {
@@ -997,6 +1185,27 @@
         mSipTransportSession1 = null;
         mSipTransportSession2 = null;
         mSipTransportSession = null;
+        mOutgoingShortCodeSms1 = null;
+        mOutgoingShortCodeSms2 = null;
+        mOutgoingShortCodeSms = null;
+        mSatelliteController1 = null;
+        mSatelliteController2 = null;
+        mSatelliteControllers = null;
+        mSatelliteSession1 = null;
+        mSatelliteSession2 = null;
+        mSatelliteSessions = null;
+        mSatelliteIncomingDatagram1 = null;
+        mSatelliteIncomingDatagram2 = null;
+        mSatelliteIncomingDatagrams = null;
+        mSatelliteOutgoingDatagram1 = null;
+        mSatelliteOutgoingDatagram2 = null;
+        mSatelliteOutgoingDatagrams = null;
+        mSatelliteProvision1 = null;
+        mSatelliteProvision2 = null;
+        mSatelliteProvisions = null;
+        mSatelliteSosMessageRecommender1 = null;
+        mSatelliteSosMessageRecommender2 = null;
+        mSatelliteSosMessageRecommenders = null;
         super.tearDown();
     }
 
@@ -1226,7 +1435,7 @@
         mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum
         VoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(100L);
 
-        // should be denied
+        // Should be denied
         assertNull(voiceCallRatUsage);
     }
 
@@ -1244,7 +1453,7 @@
                 mPersistAtomsStorage.getAtomsProto().voiceCallSessionPullTimestampMillis;
         VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(50L);
 
-        // first set of results should equal to file contents, second should be empty, corresponding
+        // First set of results should equal to file contents, second should be empty, corresponding
         // pull timestamp should be updated and saved, other fields should be unaffected
         assertProtoArrayEquals(mVoiceCallRatUsages, voiceCallRatUsage1);
         assertProtoArrayIsEmpty(voiceCallRatUsage2);
@@ -1275,7 +1484,7 @@
         mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum
         VoiceCallSession[] voiceCallSession = mPersistAtomsStorage.getVoiceCallSessions(100L);
 
-        // should be denied
+        // Should be denied
         assertNull(voiceCallSession);
     }
 
@@ -1293,7 +1502,7 @@
                 mPersistAtomsStorage.getAtomsProto().voiceCallRatUsagePullTimestampMillis;
         VoiceCallRatUsage[] voiceCallRatUsage = mPersistAtomsStorage.getVoiceCallRatUsages(50L);
 
-        // first set of results should equal to file contents, second should be empty, corresponding
+        // First set of results should equal to file contents, second should be empty, corresponding
         // pull timestamp should be updated and saved, other fields should be unaffected
         assertProtoArrayEquals(mVoiceCallSessions, voiceCallSession1);
         assertProtoArrayIsEmpty(voiceCallSession2);
@@ -1325,7 +1534,7 @@
                 mServiceState1Proto, mServiceSwitch1Proto);
         mPersistAtomsStorage.incTimeMillis(100L);
 
-        // service state and service switch should be added successfully
+        // Service state and service switch should be added successfully
         verifyCurrentStateSavedToFileOnce();
         CellularServiceState[] serviceStates = mPersistAtomsStorage.getCellularServiceStates(0L);
         CellularDataServiceSwitch[] serviceSwitches =
@@ -1348,7 +1557,7 @@
                 mServiceState2Proto, mServiceSwitch2Proto);
         mPersistAtomsStorage.incTimeMillis(100L);
 
-        // service state and service switch should be added successfully
+        // Service state and service switch should be added successfully
         verifyCurrentStateSavedToFileOnce();
         CellularServiceState[] serviceStates = mPersistAtomsStorage.getCellularServiceStates(0L);
         CellularDataServiceSwitch[] serviceSwitches =
@@ -1442,7 +1651,7 @@
         CellularDataServiceSwitch[] serviceSwitches =
                 mPersistAtomsStorage.getCellularDataServiceSwitches(100L);
 
-        // should be denied
+        // Should be denied
         assertNull(serviceSwitches);
     }
 
@@ -1459,7 +1668,7 @@
         CellularDataServiceSwitch[] serviceSwitches2 =
                 mPersistAtomsStorage.getCellularDataServiceSwitches(50L);
 
-        // first set of results should equal to file contents, second should be empty, corresponding
+        // First set of results should equal to file contents, second should be empty, corresponding
         // pull timestamp should be updated and saved
         assertProtoArrayEqualsIgnoringOrder(
                 new CellularDataServiceSwitch[] {mServiceSwitch1Proto, mServiceSwitch2Proto},
@@ -1487,7 +1696,7 @@
         mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum
         CellularServiceState[] serviceStates = mPersistAtomsStorage.getCellularServiceStates(100L);
 
-        // should be denied
+        // Should be denied
         assertNull(serviceStates);
     }
 
@@ -1502,7 +1711,7 @@
         mPersistAtomsStorage.incTimeMillis(100L);
         CellularServiceState[] serviceStates2 = mPersistAtomsStorage.getCellularServiceStates(50L);
 
-        // first set of results should equal to file contents, second should be empty, corresponding
+        // First set of results should equal to file contents, second should be empty, corresponding
         // pull timestamp should be updated and saved
         assertProtoArrayEqualsIgnoringOrder(
                 new CellularServiceState[] {
@@ -1536,7 +1745,7 @@
         mPersistAtomsStorage.addImsRegistrationStats(copyOf(mImsRegistrationStatsLte0));
         mPersistAtomsStorage.incTimeMillis(DAY_IN_MILLIS);
 
-        // service state and service switch should be added successfully
+        // Service state and service switch should be added successfully
         verifyCurrentStateSavedToFileOnce();
         ImsRegistrationStats[] regStats = mPersistAtomsStorage.getImsRegistrationStats(0L);
         assertProtoArrayEquals(new ImsRegistrationStats[] {mImsRegistrationStatsLte0}, regStats);
@@ -1552,7 +1761,7 @@
         mPersistAtomsStorage.addImsRegistrationStats(copyOf(mImsRegistrationStatsWifi0));
         mPersistAtomsStorage.incTimeMillis(DAY_IN_MILLIS);
 
-        // service state and service switch should be added successfully
+        // Service state and service switch should be added successfully
         verifyCurrentStateSavedToFileOnce();
         ImsRegistrationStats[] regStats = mPersistAtomsStorage.getImsRegistrationStats(0L);
         assertProtoArrayEqualsIgnoringOrder(
@@ -1624,7 +1833,7 @@
         mPersistAtomsStorage.addImsRegistrationTermination(mImsRegistrationTerminationLte);
         mPersistAtomsStorage.incTimeMillis(100L);
 
-        // service state and service switch should be added successfully
+        // Service state and service switch should be added successfully
         verifyCurrentStateSavedToFileOnce();
         ImsRegistrationTermination[] terminations =
                 mPersistAtomsStorage.getImsRegistrationTerminations(0L);
@@ -1642,7 +1851,7 @@
         mPersistAtomsStorage.addImsRegistrationTermination(mImsRegistrationTerminationWifi);
         mPersistAtomsStorage.incTimeMillis(100L);
 
-        // service state and service switch should be added successfully
+        // Service state and service switch should be added successfully
         verifyCurrentStateSavedToFileOnce();
         ImsRegistrationTermination[] terminations =
                 mPersistAtomsStorage.getImsRegistrationTerminations(0L);
@@ -1707,7 +1916,7 @@
         mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum
         ImsRegistrationStats[] stats = mPersistAtomsStorage.getImsRegistrationStats(100L);
 
-        // should be denied
+        // Should be denied
         assertNull(stats);
     }
 
@@ -1722,7 +1931,7 @@
         mPersistAtomsStorage.incTimeMillis(100L);
         ImsRegistrationStats[] stats2 = mPersistAtomsStorage.getImsRegistrationStats(50L);
 
-        // first set of results should equal to file contents, second should be empty, corresponding
+        // First set of results should equal to file contents, second should be empty, corresponding
         // pull timestamp should be updated and saved
         assertProtoArrayEqualsIgnoringOrder(
                 new ImsRegistrationStats[] {
@@ -1753,7 +1962,7 @@
         ImsRegistrationTermination[] terminations =
                 mPersistAtomsStorage.getImsRegistrationTerminations(100L);
 
-        // should be denied
+        // Should be denied
         assertNull(terminations);
     }
 
@@ -1770,7 +1979,7 @@
         ImsRegistrationTermination[] terminations2 =
                 mPersistAtomsStorage.getImsRegistrationTerminations(50L);
 
-        // first set of results should equal to file contents, second should be empty, corresponding
+        // First set of results should equal to file contents, second should be empty, corresponding
         // pull timestamp should be updated and saved
         assertProtoArrayEqualsIgnoringOrder(
                 new ImsRegistrationTermination[] {
@@ -1935,7 +2144,7 @@
         ImsRegistrationFeatureTagStats[] result =
                 mPersistAtomsStorage.getImsRegistrationFeatureTagStats(100L);
 
-        // should be denied
+        // Should be denied
         assertNull(result);
     }
 
@@ -1952,7 +2161,7 @@
         ImsRegistrationFeatureTagStats[] statses2 =
                 mPersistAtomsStorage.getImsRegistrationFeatureTagStats(50L);
 
-        // first results of get should have two atoms, second should be empty
+        // First results of get should have two atoms, second should be empty
         // pull timestamp should be updated and saved
         assertProtoArrayEqualsIgnoringOrder(mImsRegistrationFeatureTagStatses, statses1);
         assertProtoArrayEquals(new ImsRegistrationFeatureTagStats[0], statses2);
@@ -2002,13 +2211,13 @@
 
         mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
 
-        // store 11 same atoms, but only 1 atoms stored with count 11
+        // Store 11 same atoms, but only 1 atoms stored with count 11
         for (int i = 0; i < 11; i++) {
             mPersistAtomsStorage
                     .addRcsClientProvisioningStats(mRcsClientProvisioningStats1Proto);
             mPersistAtomsStorage.incTimeMillis(100L);
         }
-        // store 1 different atom and count 1
+        // Store 1 different atom and count 1
         mPersistAtomsStorage
                 .addRcsClientProvisioningStats(mRcsClientProvisioningStats2Proto);
 
@@ -2017,7 +2226,7 @@
         RcsClientProvisioningStats[] result =
                 mPersistAtomsStorage.getRcsClientProvisioningStats(0L);
 
-        // first atom has count 11, the other has 1
+        // First atom has count 11, the other has 1
         assertHasStatsAndCount(result, mRcsClientProvisioningStats1Proto, 11);
         assertHasStatsAndCount(result, mRcsClientProvisioningStats2Proto, 1);
     }
@@ -2032,7 +2241,7 @@
         RcsClientProvisioningStats[] result =
                 mPersistAtomsStorage.getRcsClientProvisioningStats(100L);
 
-        // should be denied
+        // Should be denied
         assertNull(result);
     }
 
@@ -2049,7 +2258,7 @@
         RcsClientProvisioningStats[] statses2 =
                 mPersistAtomsStorage.getRcsClientProvisioningStats(50L);
 
-        // first results of get should have two atoms, second should be empty
+        // First results of get should have two atoms, second should be empty
         // pull timestamp should be updated and saved
         assertProtoArrayEqualsIgnoringOrder(mRcsClientProvisioningStatses, statses1);
         assertProtoArrayEquals(new RcsClientProvisioningStats[0], statses2);
@@ -2101,8 +2310,8 @@
 
         mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
 
-        // store 5 same atoms (1Proto), but only 1 atoms stored with count 5, total time 2000L * 5
-        // store 5 same atoms (2Proto), but only 1 atoms stored with count 5, total time 2000L * 5
+        // Store 5 same atoms (1Proto), but only 1 atoms stored with count 5, total time 2000L * 5
+        // Store 5 same atoms (2Proto), but only 1 atoms stored with count 5, total time 2000L * 5
 
         for (int i = 0; i < maxCount; i++) {
             mPersistAtomsStorage
@@ -2140,7 +2349,7 @@
         RcsAcsProvisioningStats[] result =
                 mPersistAtomsStorage.getRcsAcsProvisioningStats(100L);
 
-        // should be denied
+        // Should be denied
         assertNull(result);
     }
 
@@ -2157,7 +2366,7 @@
         RcsAcsProvisioningStats[] statses2 =
                 mPersistAtomsStorage.getRcsAcsProvisioningStats(DAY_IN_MILLIS - HOUR_IN_MILLIS);
 
-        // first results of get should have two atoms, second should be empty
+        // First results of get should have two atoms, second should be empty
         // pull timestamp should be updated and saved
         assertProtoArrayEqualsIgnoringOrder(mRcsAcsProvisioningStatses, statses1);
         assertProtoArrayEquals(new RcsAcsProvisioningStats[0], statses2);
@@ -2183,7 +2392,7 @@
         mPersistAtomsStorage.addImsRegistrationServiceDescStats(mImsRegistrationServiceIm);
         mPersistAtomsStorage.incTimeMillis(100L);
 
-        // service state and service switch should be added successfully
+        // Service state and service switch should be added successfully
         verifyCurrentStateSavedToFileOnce();
         ImsRegistrationServiceDescStats[] outputs =
             mPersistAtomsStorage.getImsRegistrationServiceDescStats(0L);
@@ -2201,7 +2410,7 @@
         mPersistAtomsStorage.addImsRegistrationServiceDescStats(mImsRegistrationServiceFt);
         mPersistAtomsStorage.incTimeMillis(100L);
 
-        // service state and service switch should be added successfully
+        // Service state and service switch should be added successfully
         verifyCurrentStateSavedToFileOnce();
         ImsRegistrationServiceDescStats[] output =
             mPersistAtomsStorage.getImsRegistrationServiceDescStats(0L);
@@ -2248,7 +2457,7 @@
         ImsRegistrationServiceDescStats[] output =
             mPersistAtomsStorage.getImsRegistrationServiceDescStats(100L);
 
-        // should be denied
+        // Should be denied
         assertNull(output);
     }
 
@@ -2265,7 +2474,7 @@
         ImsRegistrationServiceDescStats[] output2 =
             mPersistAtomsStorage.getImsRegistrationServiceDescStats(50L);
 
-        // first set of results should equal to file contents, second should be empty, corresponding
+        // First set of results should equal to file contents, second should be empty, corresponding
         // pull timestamp should be updated and saved
         assertProtoArrayEqualsIgnoringOrder(
                 new ImsRegistrationServiceDescStats[] {
@@ -2436,7 +2645,7 @@
         mPersistAtomsStorage.addImsDedicatedBearerListenerEvent(mImsDedicatedBearerListenerEvent1);
         mPersistAtomsStorage.incTimeMillis(100L);
 
-        // service state and service switch should be added successfully
+        // Service state and service switch should be added successfully
         verifyCurrentStateSavedToFileOnce();
         ImsDedicatedBearerListenerEvent[] outputs =
                 mPersistAtomsStorage.getImsDedicatedBearerListenerEvent(0L);
@@ -2454,7 +2663,7 @@
         mPersistAtomsStorage.addImsDedicatedBearerListenerEvent(mImsDedicatedBearerListenerEvent2);
         mPersistAtomsStorage.incTimeMillis(100L);
 
-        // service state and service switch should be added successfully
+        // Service state and service switch should be added successfully
         verifyCurrentStateSavedToFileOnce();
         ImsDedicatedBearerListenerEvent[] output =
                 mPersistAtomsStorage.getImsDedicatedBearerListenerEvent(0L);
@@ -2495,7 +2704,7 @@
         mPersistAtomsStorage.addImsDedicatedBearerEvent(mImsDedicatedBearerEvent1);
         mPersistAtomsStorage.incTimeMillis(100L);
 
-        // service state and service switch should be added successfully
+        // Service state and service switch should be added successfully
         verifyCurrentStateSavedToFileOnce();
         ImsDedicatedBearerEvent[] outputs =
             mPersistAtomsStorage.getImsDedicatedBearerEvent(0L);
@@ -2513,7 +2722,7 @@
         mPersistAtomsStorage.addImsDedicatedBearerEvent(mImsDedicatedBearerEvent2);
         mPersistAtomsStorage.incTimeMillis(100L);
 
-        // service state and service switch should be added successfully
+        // Service state and service switch should be added successfully
         verifyCurrentStateSavedToFileOnce();
         ImsDedicatedBearerEvent[] output =
             mPersistAtomsStorage.getImsDedicatedBearerEvent(0L);
@@ -2588,7 +2797,6 @@
                 mImsDedicatedBearerEvent2, newStats}, stats);
     }
 
-
     @Test
     @SmallTest
     public void addUceEventStats_emptyProto() throws Exception {
@@ -2597,7 +2805,7 @@
         mPersistAtomsStorage.addUceEventStats(mUceEventStats1);
         mPersistAtomsStorage.incTimeMillis(100L);
 
-        // service state and service switch should be added successfully
+        // Service state and service switch should be added successfully
         verifyCurrentStateSavedToFileOnce();
         UceEventStats[] outputs = mPersistAtomsStorage.getUceEventStats(0L);
         assertProtoArrayEquals(
@@ -2614,7 +2822,7 @@
         mPersistAtomsStorage.addUceEventStats(mUceEventStats2);
         mPersistAtomsStorage.incTimeMillis(100L);
 
-        // service state and service switch should be added successfully
+        // Service state and service switch should be added successfully
         verifyCurrentStateSavedToFileOnce();
         UceEventStats[] output = mPersistAtomsStorage.getUceEventStats(0L);
         assertProtoArrayEqualsIgnoringOrder(
@@ -2688,7 +2896,7 @@
         mPersistAtomsStorage.addPresenceNotifyEvent(mPresenceNotifyEvent2);
         mPersistAtomsStorage.incTimeMillis(100L);
 
-        // service state and service switch should be added successfully
+        // Service state and service switch should be added successfully
         verifyCurrentStateSavedToFileOnce();
         PresenceNotifyEvent[] output = mPersistAtomsStorage.getPresenceNotifyEvent(0L);
         assertProtoArrayEqualsIgnoringOrder(
@@ -2704,7 +2912,7 @@
         mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum
         PresenceNotifyEvent[] output = mPersistAtomsStorage.getPresenceNotifyEvent(100L);
 
-        // should be denied
+        // Should be denied
         assertNull(output);
     }
 
@@ -2719,7 +2927,7 @@
         mPersistAtomsStorage.incTimeMillis(100L);
         PresenceNotifyEvent[] output2 = mPersistAtomsStorage.getPresenceNotifyEvent(50L);
 
-        // first set of results should equal to file contents, second should be empty, corresponding
+        // First set of results should equal to file contents, second should be empty, corresponding
         // pull timestamp should be updated and saved
         assertProtoArrayEqualsIgnoringOrder(
                 new PresenceNotifyEvent[] {mPresenceNotifyEvent1, mPresenceNotifyEvent2}, output1);
@@ -2846,7 +3054,7 @@
         SipTransportFeatureTagStats[] outputs =
                 mPersistAtomsStorage.getSipTransportFeatureTagStats(100L);
 
-        // should be denied
+        // Should be denied
         assertNull(outputs);
     }
 
@@ -2865,7 +3073,7 @@
         SipTransportFeatureTagStats[] output2 =
                 mPersistAtomsStorage.getSipTransportFeatureTagStats(50L);
 
-        // first set of results should equal to file contents, second should be empty, corresponding
+        // First set of results should equal to file contents, second should be empty, corresponding
         // pull timestamp should be updated and saved
         assertProtoArrayEqualsIgnoringOrder(
                 new SipTransportFeatureTagStats[] {
@@ -2988,7 +3196,7 @@
         mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum
 
         SipDelegateStats[] outputs = mPersistAtomsStorage.getSipDelegateStats(100L);
-        // should be denied
+        // Should be denied
         assertNull(outputs);
     }
 
@@ -3005,7 +3213,7 @@
         mPersistAtomsStorage.incTimeMillis(100L);
         SipDelegateStats[] output2 = mPersistAtomsStorage.getSipDelegateStats(50L);
 
-        // first set of results should equal to file contents, second should be empty, corresponding
+        // First set of results should equal to file contents, second should be empty, corresponding
         // pull timestamp should be updated and saved
         assertProtoArrayEqualsIgnoringOrder(
                 new SipDelegateStats[] {
@@ -3131,18 +3339,18 @@
         createEmptyTestFile();
         mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
 
-        // store 11 same atoms, but only 1 atoms stored with count 11
+        // Store 11 same atoms, but only 1 atoms stored with count 11
         for (int i = 0; i < 11; i++) {
             mPersistAtomsStorage.addSipMessageResponse(mSipMessageResponse1);
             mPersistAtomsStorage.incTimeMillis(100L);
         }
-        // store 1 different atom and count 1
+        // Store 1 different atom and count 1
         mPersistAtomsStorage.addSipMessageResponse(mSipMessageResponse2);
 
         verifyCurrentStateSavedToFileOnce();
         SipMessageResponse[] result = mPersistAtomsStorage.getSipMessageResponse(0L);
 
-        // first atom has count 11, the other has 1
+        // First atom has count 11, the other has 1
         assertHasStats(result, mSipMessageResponse1, 11);
         assertHasStats(result, mSipMessageResponse2, 1);
     }
@@ -3200,18 +3408,18 @@
         createEmptyTestFile();
         mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
 
-        // store 11 same atoms, but only 1 atoms stored with count 11
+        // Store 11 same atoms, but only 1 atoms stored with count 11
         for (int i = 0; i < 11; i++) {
             mPersistAtomsStorage.addCompleteSipTransportSession(mSipTransportSession1);
             mPersistAtomsStorage.incTimeMillis(100L);
         }
-        // store 1 different atom and count 1
+        // Store 1 different atom and count 1
         mPersistAtomsStorage.addCompleteSipTransportSession(mSipTransportSession2);
 
         verifyCurrentStateSavedToFileOnce();
         SipTransportSession[] result = mPersistAtomsStorage.getSipTransportSession(0L);
 
-        // first atom has count 11, the other has 1
+        // First atom has count 11, the other has 1
         assertHasStats(result, mSipTransportSession1, 11);
         assertHasStats(result, mSipTransportSession2, 1);
     }
@@ -3399,6 +3607,599 @@
     }
 
     @Test
+    public void addOutgoingShortCodeSms_emptyProto() throws Exception {
+        createEmptyTestFile();
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addOutgoingShortCodeSms(mOutgoingShortCodeSms1);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // OutgoingShortCodeSms should be added successfully, changes should be saved.
+        verifyCurrentStateSavedToFileOnce();
+        OutgoingShortCodeSms[] expectedList = new OutgoingShortCodeSms[] {mOutgoingShortCodeSms1};
+        assertProtoArrayEquals(expectedList,
+                mPersistAtomsStorage.getOutgoingShortCodeSms(0L));
+    }
+
+    @Test
+    public void addOutgoingShortCodeSms_withExistingEntries() throws Exception {
+        createEmptyTestFile();
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addOutgoingShortCodeSms(mOutgoingShortCodeSms1);
+        mPersistAtomsStorage.addOutgoingShortCodeSms(mOutgoingShortCodeSms2);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // OutgoingShortCodeSms should be added successfully.
+        verifyCurrentStateSavedToFileOnce();
+        OutgoingShortCodeSms[] expectedList = new OutgoingShortCodeSms[] {mOutgoingShortCodeSms1,
+                mOutgoingShortCodeSms2};
+        assertProtoArrayEqualsIgnoringOrder(expectedList,
+                mPersistAtomsStorage.getOutgoingShortCodeSms(0L));
+    }
+
+    @Test
+    public void addOutgoingShortCodeSms_updateExistingEntries() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        // Add copy of mOutgoingShortCodeSms1.
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addOutgoingShortCodeSms(copyOf(mOutgoingShortCodeSms1));
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // mOutgoingShortCodeSms1's short code sms count should be increased by 1.
+        verifyCurrentStateSavedToFileOnce();
+        OutgoingShortCodeSms newOutgoingShortCodeSms1 = copyOf(mOutgoingShortCodeSms1);
+        newOutgoingShortCodeSms1.shortCodeSmsCount = 2;
+        OutgoingShortCodeSms[] expectedList = new OutgoingShortCodeSms[] {newOutgoingShortCodeSms1,
+                mOutgoingShortCodeSms2};
+        assertProtoArrayEqualsIgnoringOrder(expectedList,
+                mPersistAtomsStorage.getOutgoingShortCodeSms(0L));
+    }
+
+    @Test
+    public void addOutgoingShortCodeSms_tooManyEntries() throws Exception {
+        createEmptyTestFile();
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+
+        // Store mOutgoingShortCodeSms1 11 times.
+        for (int i = 0; i < 11; i++) {
+            mPersistAtomsStorage.addOutgoingShortCodeSms(mOutgoingShortCodeSms1);
+            mPersistAtomsStorage.incTimeMillis(100L);
+        }
+        // Store mOutgoingShortCodeSms2 1 time.
+        mPersistAtomsStorage.addOutgoingShortCodeSms(mOutgoingShortCodeSms2);
+
+        verifyCurrentStateSavedToFileOnce();
+        OutgoingShortCodeSms[] result = mPersistAtomsStorage
+                .getOutgoingShortCodeSms(0L);
+        assertHasStatsAndCount(result, mOutgoingShortCodeSms1, 11);
+        assertHasStatsAndCount(result, mOutgoingShortCodeSms2, 1);
+    }
+
+    @Test
+    public void getOutgoingShortCodeSms_tooFrequent() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        // Pull interval less than minimum.
+        mPersistAtomsStorage.incTimeMillis(50L);
+        OutgoingShortCodeSms[] outgoingShortCodeSmsList = mPersistAtomsStorage
+                .getOutgoingShortCodeSms(100L);
+        // Should be denied.
+        assertNull(outgoingShortCodeSmsList);
+    }
+
+    @Test
+    public void getOutgoingShortCodeSms_withSavedAtoms() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(100L);
+        OutgoingShortCodeSms[] outgoingShortCodeSmsList1 = mPersistAtomsStorage
+                .getOutgoingShortCodeSms(50L);
+        mPersistAtomsStorage.incTimeMillis(100L);
+        OutgoingShortCodeSms[] outgoingShortCodeSmsList2 = mPersistAtomsStorage
+                .getOutgoingShortCodeSms(50L);
+
+        // First set of results should be equal to file contents.
+        OutgoingShortCodeSms[] expectedOutgoingShortCodeSmsList =
+                new OutgoingShortCodeSms[] {mOutgoingShortCodeSms1, mOutgoingShortCodeSms2};
+        assertProtoArrayEqualsIgnoringOrder(expectedOutgoingShortCodeSmsList,
+                outgoingShortCodeSmsList1);
+        // Second set of results should be empty.
+        assertProtoArrayEquals(new OutgoingShortCodeSms[0], outgoingShortCodeSmsList2);
+        // Corresponding pull timestamp should be updated and saved.
+        assertEquals(START_TIME_MILLIS + 200L, mPersistAtomsStorage
+                .getAtomsProto().outgoingShortCodeSmsPullTimestampMillis);
+        InOrder inOrder = inOrder(mTestFileOutputStream);
+        assertEquals(START_TIME_MILLIS + 100L,
+                getAtomsWritten(inOrder).outgoingShortCodeSmsPullTimestampMillis);
+        assertEquals(START_TIME_MILLIS + 200L,
+                getAtomsWritten(inOrder).outgoingShortCodeSmsPullTimestampMillis);
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void addSatelliteControllerStats_emptyProto() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addSatelliteControllerStats(mSatelliteController1);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // Service state and service switch should be added successfully
+        verifyCurrentStateSavedToFileOnce();
+        SatelliteController[] output =
+                mPersistAtomsStorage.getSatelliteControllerStats(0L);
+        assertProtoArrayEquals(
+                new SatelliteController[] {mSatelliteController1}, output);
+    }
+
+    @Test
+    public void addSatelliteControllerStats_withExistingEntries() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addSatelliteControllerStats(mSatelliteController1);
+        mPersistAtomsStorage.addSatelliteControllerStats(mSatelliteController2);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        SatelliteController expected = new SatelliteController();
+        expected.countOfSatelliteServiceEnablementsSuccess =
+                mSatelliteController1.countOfSatelliteServiceEnablementsSuccess
+                        + mSatelliteController2.countOfSatelliteServiceEnablementsSuccess;
+        expected.countOfSatelliteServiceEnablementsFail =
+                mSatelliteController1.countOfSatelliteServiceEnablementsFail
+                        + mSatelliteController2.countOfSatelliteServiceEnablementsFail;
+        expected.countOfOutgoingDatagramSuccess =
+                mSatelliteController1.countOfOutgoingDatagramSuccess
+                        + mSatelliteController2.countOfOutgoingDatagramSuccess;
+        expected.countOfOutgoingDatagramFail =
+                mSatelliteController1.countOfOutgoingDatagramFail
+                        + mSatelliteController2.countOfOutgoingDatagramFail;
+        expected.countOfIncomingDatagramSuccess =
+                mSatelliteController1.countOfIncomingDatagramSuccess
+                        + mSatelliteController2.countOfIncomingDatagramSuccess;
+        expected.countOfIncomingDatagramFail =
+                mSatelliteController1.countOfIncomingDatagramFail
+                        + mSatelliteController2.countOfIncomingDatagramFail;
+        expected.countOfDatagramTypeSosSmsSuccess =
+                mSatelliteController1.countOfDatagramTypeSosSmsSuccess
+                        + mSatelliteController2.countOfDatagramTypeSosSmsSuccess;
+        expected.countOfDatagramTypeSosSmsFail =
+                mSatelliteController1.countOfDatagramTypeSosSmsFail
+                        + mSatelliteController2.countOfDatagramTypeSosSmsFail;
+        expected.countOfDatagramTypeLocationSharingSuccess =
+                mSatelliteController1.countOfDatagramTypeLocationSharingSuccess
+                        + mSatelliteController2.countOfDatagramTypeLocationSharingSuccess;
+        expected.countOfDatagramTypeLocationSharingFail =
+                mSatelliteController1.countOfDatagramTypeLocationSharingFail
+                        + mSatelliteController2.countOfDatagramTypeLocationSharingFail;
+        expected.countOfProvisionSuccess =
+                mSatelliteController1.countOfProvisionSuccess
+                        + mSatelliteController2.countOfProvisionSuccess;
+        expected.countOfProvisionFail =
+                mSatelliteController1.countOfProvisionFail
+                        + mSatelliteController2.countOfProvisionFail;
+        expected.countOfDeprovisionSuccess =
+                mSatelliteController1.countOfDeprovisionSuccess
+                        + mSatelliteController2.countOfDeprovisionSuccess;
+        expected.countOfDeprovisionFail =
+                mSatelliteController1.countOfDeprovisionFail
+                        + mSatelliteController2.countOfDeprovisionFail;
+        expected.totalServiceUptimeSec =
+                mSatelliteController1.totalServiceUptimeSec
+                        + mSatelliteController2.totalServiceUptimeSec;
+        expected.totalBatteryConsumptionPercent =
+                mSatelliteController1.totalBatteryConsumptionPercent
+                        + mSatelliteController2.totalBatteryConsumptionPercent;
+        expected.totalBatteryChargedTimeSec =
+                mSatelliteController1.totalBatteryChargedTimeSec
+                        + mSatelliteController2.totalBatteryChargedTimeSec;
+
+        // Service state and service switch should be added successfully
+        verifyCurrentStateSavedToFileOnce();
+        SatelliteController[] output =
+                mPersistAtomsStorage.getSatelliteControllerStats(0L);
+        assertHasStats(output, expected);
+    }
+
+    @Test
+    public void getSatelliteControllerStats_tooFrequent() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum
+        SatelliteController[] output =
+                mPersistAtomsStorage.getSatelliteControllerStats(100L);
+
+        // Should be denied
+        assertNull(output);
+    }
+
+    @Test
+    public void addSatelliteSessionStats_emptyProto() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addSatelliteSessionStats(
+                mSatelliteSession1);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // Service state and service switch should be added successfully
+        verifyCurrentStateSavedToFileOnce();
+        SatelliteSession[] output =
+                mPersistAtomsStorage.getSatelliteSessionStats(0L);
+        assertProtoArrayEquals(
+                new SatelliteSession[] {mSatelliteSession1}, output);
+    }
+
+    @Test
+    public void addSatelliteSessionStats_withExistingEntries() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addSatelliteSessionStats(
+                mSatelliteSession1);
+        mPersistAtomsStorage.addSatelliteSessionStats(
+                mSatelliteSession2);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // Service state and service switch should be added successfully
+        verifyCurrentStateSavedToFileOnce();
+        SatelliteSession[] output =
+                mPersistAtomsStorage.getSatelliteSessionStats(0L);
+        assertProtoArrayEqualsIgnoringOrder(
+                new SatelliteSession[] {
+                        mSatelliteSession1, mSatelliteSession2},
+                output);
+    }
+
+    @Test
+    public void addSatelliteSessionStats_tooManyEntries() throws Exception {
+        createEmptyTestFile();
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+
+        // Store atoms up to maximum number + 1
+        int maxCount = 15 + 1;
+        for (int i = 0; i < maxCount; i++) {
+            mPersistAtomsStorage
+                    .addSatelliteSessionStats(
+                            copyOf(mSatelliteSession1));
+            mPersistAtomsStorage.incTimeMillis(100L);
+        }
+
+        // Store 1 different atom
+        mPersistAtomsStorage
+                .addSatelliteSessionStats(mSatelliteSession2);
+
+        verifyCurrentStateSavedToFileOnce();
+
+        SatelliteSession[] result =
+                mPersistAtomsStorage.getSatelliteSessionStats(0L);
+
+        // First atom has count 16, the other has 1
+        assertHasStatsAndCount(result, mSatelliteSession1, 16);
+        assertHasStatsAndCount(result, mSatelliteSession2, 1);
+
+    }
+
+    @Test
+    public void getSatelliteSessionStats_tooFrequent() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum
+        SatelliteSession[] output =
+                mPersistAtomsStorage.getSatelliteSessionStats(100L);
+
+        // Should be denied
+        assertNull(output);
+    }
+
+
+
+    @Test
+    public void addSatelliteIncomingDatagramStats_emptyProto() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addSatelliteIncomingDatagramStats(mSatelliteIncomingDatagram1);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // Service state and service switch should be added successfully
+        verifyCurrentStateSavedToFileOnce();
+        SatelliteIncomingDatagram[] output =
+                mPersistAtomsStorage.getSatelliteIncomingDatagramStats(0L);
+        assertProtoArrayEquals(
+                new SatelliteIncomingDatagram[] {mSatelliteIncomingDatagram1}, output);
+    }
+
+    @Test
+    public void addSatelliteIncomingDatagramStats_withExistingEntries() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addSatelliteIncomingDatagramStats(mSatelliteIncomingDatagram1);
+        mPersistAtomsStorage.addSatelliteIncomingDatagramStats(mSatelliteIncomingDatagram2);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // Service state and service switch should be added successfully
+        verifyCurrentStateSavedToFileOnce();
+        SatelliteIncomingDatagram[] output =
+                mPersistAtomsStorage.getSatelliteIncomingDatagramStats(0L);
+        assertProtoArrayEqualsIgnoringOrder(
+                new SatelliteIncomingDatagram[] {
+                        mSatelliteIncomingDatagram1, mSatelliteIncomingDatagram2}, output);
+    }
+
+    @Test
+    public void addSatelliteIncomingDatagramStats_tooManyEntries() throws Exception {
+        createEmptyTestFile();
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+
+        // Store atoms up to maximum number + 1
+        int maxCount = 15 + 1;
+        for (int i = 0; i < maxCount; i++) {
+            mPersistAtomsStorage
+                    .addSatelliteIncomingDatagramStats(copyOf(mSatelliteIncomingDatagram1));
+            mPersistAtomsStorage.incTimeMillis(100L);
+        }
+
+        // Store 1 different atom
+        mPersistAtomsStorage
+                .addSatelliteIncomingDatagramStats(mSatelliteIncomingDatagram2);
+
+        verifyCurrentStateSavedToFileOnce();
+
+        SatelliteIncomingDatagram[] result =
+                mPersistAtomsStorage.getSatelliteIncomingDatagramStats(0L);
+
+        // First atom has count 14, the other has 1
+        assertHasStatsAndCount(result, mSatelliteIncomingDatagram1, 14);
+        assertHasStatsAndCount(result, mSatelliteIncomingDatagram2, 1);
+
+    }
+
+    @Test
+    public void getSatelliteIncomingDatagramStats_tooFrequent() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum
+        SatelliteIncomingDatagram[] output =
+                mPersistAtomsStorage.getSatelliteIncomingDatagramStats(100L);
+
+        // Should be denied
+        assertNull(output);
+    }
+
+    @Test
+    public void addSatelliteOutgoingDatagramStats_emptyProto() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addSatelliteOutgoingDatagramStats(mSatelliteOutgoingDatagram1);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // Service state and service switch should be added successfully
+        verifyCurrentStateSavedToFileOnce();
+        SatelliteOutgoingDatagram[] output =
+                mPersistAtomsStorage.getSatelliteOutgoingDatagramStats(0L);
+        assertProtoArrayEquals(
+                new SatelliteOutgoingDatagram[] {mSatelliteOutgoingDatagram1}, output);
+    }
+
+    @Test
+    public void addSatelliteOutgoingDatagramStats_withExistingEntries() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addSatelliteOutgoingDatagramStats(mSatelliteOutgoingDatagram1);
+        mPersistAtomsStorage.addSatelliteOutgoingDatagramStats(mSatelliteOutgoingDatagram2);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // Service state and service switch should be added successfully
+        verifyCurrentStateSavedToFileOnce();
+        SatelliteOutgoingDatagram[] output =
+                mPersistAtomsStorage.getSatelliteOutgoingDatagramStats(0L);
+        assertProtoArrayEqualsIgnoringOrder(
+                new SatelliteOutgoingDatagram[] {
+                        mSatelliteOutgoingDatagram1, mSatelliteOutgoingDatagram2}, output);
+    }
+
+    @Test
+    public void addSatelliteOutgoingDatagramStats_tooManyEntries() throws Exception {
+        createEmptyTestFile();
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+
+        // Store atoms up to maximum number + 1
+        int maxCount = 15 + 1;
+        for (int i = 0; i < maxCount; i++) {
+            mPersistAtomsStorage
+                    .addSatelliteOutgoingDatagramStats(copyOf(mSatelliteOutgoingDatagram1));
+            mPersistAtomsStorage.incTimeMillis(100L);
+        }
+
+        // Store 1 different atom
+        mPersistAtomsStorage
+                .addSatelliteOutgoingDatagramStats(mSatelliteOutgoingDatagram2);
+
+        verifyCurrentStateSavedToFileOnce();
+
+        SatelliteOutgoingDatagram[] result =
+                mPersistAtomsStorage.getSatelliteOutgoingDatagramStats(0L);
+
+        // First atom has count 14, the other has 1
+        assertHasStatsAndCount(result, mSatelliteOutgoingDatagram1, 14);
+        assertHasStatsAndCount(result, mSatelliteOutgoingDatagram2, 1);
+
+    }
+
+    @Test
+    public void getSatelliteOutgoingDatagramStats_tooFrequent() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum
+        SatelliteOutgoingDatagram[] output =
+                mPersistAtomsStorage.getSatelliteOutgoingDatagramStats(100L);
+
+        // Should be denied
+        assertNull(output);
+    }
+
+    @Test
+    public void addSatelliteProvisionStats_emptyProto() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addSatelliteProvisionStats(mSatelliteProvision1);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // Service state and service switch should be added successfully
+        verifyCurrentStateSavedToFileOnce();
+        SatelliteProvision[] output =
+                mPersistAtomsStorage.getSatelliteProvisionStats(0L);
+        assertProtoArrayEquals(
+                new SatelliteProvision[] {mSatelliteProvision1}, output);
+    }
+
+    @Test
+    public void addSatelliteProvisionStats_withExistingEntries() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addSatelliteProvisionStats(mSatelliteProvision1);
+        mPersistAtomsStorage.addSatelliteProvisionStats(mSatelliteProvision2);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // Service state and service switch should be added successfully
+        verifyCurrentStateSavedToFileOnce();
+        SatelliteProvision[] output =
+                mPersistAtomsStorage.getSatelliteProvisionStats(0L);
+        assertProtoArrayEqualsIgnoringOrder(
+                new SatelliteProvision[] {
+                        mSatelliteProvision1, mSatelliteProvision2}, output);
+    }
+
+    @Test
+    public void addSatelliteProvisionStats_tooManyEntries() throws Exception {
+        createEmptyTestFile();
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+
+        // Store atoms up to maximum number + 1
+        int maxCount = 15 + 1;
+        for (int i = 0; i < maxCount; i++) {
+            mPersistAtomsStorage
+                    .addSatelliteProvisionStats(copyOf(mSatelliteProvision1));
+            mPersistAtomsStorage.incTimeMillis(100L);
+        }
+
+        // Store 1 different atom
+        mPersistAtomsStorage
+                .addSatelliteProvisionStats(mSatelliteProvision2);
+
+        verifyCurrentStateSavedToFileOnce();
+
+        SatelliteProvision[] result =
+                mPersistAtomsStorage.getSatelliteProvisionStats(0L);
+
+        // First atom has count 14, the other has 1
+        assertHasStatsAndCount(result, mSatelliteProvision1, 14);
+        assertHasStatsAndCount(result, mSatelliteProvision2, 1);
+
+    }
+
+    @Test
+    public void getSatelliteProvisionStats_tooFrequent() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum
+        SatelliteProvision[] output =
+                mPersistAtomsStorage.getSatelliteProvisionStats(100L);
+
+        // Should be denied
+        assertNull(output);
+    }
+
+    @Test
+    public void addSatelliteSosMessageRecommenderStats_emptyProto() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addSatelliteSosMessageRecommenderStats(
+                mSatelliteSosMessageRecommender1);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // Service state and service switch should be added successfully
+        verifyCurrentStateSavedToFileOnce();
+        SatelliteSosMessageRecommender[] output =
+                mPersistAtomsStorage.getSatelliteSosMessageRecommenderStats(0L);
+        assertProtoArrayEquals(
+                new SatelliteSosMessageRecommender[] {mSatelliteSosMessageRecommender1}, output);
+    }
+
+    @Test
+    public void addSatelliteSosMessageRecommenderStats_withExistingEntries() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addSatelliteSosMessageRecommenderStats(
+                mSatelliteSosMessageRecommender1);
+        mPersistAtomsStorage.addSatelliteSosMessageRecommenderStats(
+                mSatelliteSosMessageRecommender2);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // Service state and service switch should be added successfully
+        verifyCurrentStateSavedToFileOnce();
+        SatelliteSosMessageRecommender[] output =
+                mPersistAtomsStorage.getSatelliteSosMessageRecommenderStats(0L);
+        assertProtoArrayEqualsIgnoringOrder(
+                new SatelliteSosMessageRecommender[] {
+                        mSatelliteSosMessageRecommender1, mSatelliteSosMessageRecommender2},
+                output);
+    }
+
+    @Test
+    public void addSatelliteSosMessageRecommenderStats_tooManyEntries() throws Exception {
+        createEmptyTestFile();
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+
+        // Store atoms up to maximum number + 1
+        int maxCount = 15 + 1;
+        for (int i = 0; i < maxCount; i++) {
+            mPersistAtomsStorage
+                    .addSatelliteSosMessageRecommenderStats(
+                            copyOf(mSatelliteSosMessageRecommender1));
+            mPersistAtomsStorage.incTimeMillis(100L);
+        }
+
+        // Store 1 different atom
+        mPersistAtomsStorage
+                .addSatelliteSosMessageRecommenderStats(mSatelliteSosMessageRecommender2);
+
+        verifyCurrentStateSavedToFileOnce();
+
+        SatelliteSosMessageRecommender[] result =
+                mPersistAtomsStorage.getSatelliteSosMessageRecommenderStats(0L);
+
+        // First atom has count 16, the other has 1
+        assertHasStatsAndCount(result, mSatelliteSosMessageRecommender1, 16);
+        assertHasStatsAndCount(result, mSatelliteSosMessageRecommender2, 1);
+
+    }
+
+    @Test
+    public void getSatelliteSosMessageRecommenderStats_tooFrequent() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum
+        SatelliteSosMessageRecommender[] output =
+                mPersistAtomsStorage.getSatelliteSosMessageRecommenderStats(100L);
+
+        // Should be denied
+        assertNull(output);
+    }
+
+    @Test
     @SmallTest
     public void clearAtoms() throws Exception {
         createTestFile(START_TIME_MILLIS);
@@ -3469,6 +4270,20 @@
         atoms.sipMessageResponse = mSipMessageResponse;
         atoms.sipTransportSessionPullTimestampMillis = lastPullTimeMillis;
         atoms.sipTransportSession = mSipTransportSession;
+        atoms.outgoingShortCodeSms = mOutgoingShortCodeSms;
+        atoms.outgoingShortCodeSmsPullTimestampMillis = lastPullTimeMillis;
+        atoms.satelliteController = mSatelliteControllers;
+        atoms.satelliteControllerPullTimestampMillis = lastPullTimeMillis;
+        atoms.satelliteSession = mSatelliteSessions;
+        atoms.satelliteSessionPullTimestampMillis = lastPullTimeMillis;
+        atoms.satelliteIncomingDatagram = mSatelliteIncomingDatagrams;
+        atoms.satelliteIncomingDatagramPullTimestampMillis = lastPullTimeMillis;
+        atoms.satelliteOutgoingDatagram = mSatelliteOutgoingDatagrams;
+        atoms.satelliteOutgoingDatagramPullTimestampMillis = lastPullTimeMillis;
+        atoms.satelliteProvision = mSatelliteProvisions;
+        atoms.satelliteProvisionPullTimestampMillis = lastPullTimeMillis;
+        atoms.satelliteSosMessageRecommender = mSatelliteSosMessageRecommenders;
+        atoms.satelliteSosMessageRecommenderPullTimestampMillis = lastPullTimeMillis;
         FileOutputStream stream = new FileOutputStream(mTestFile);
         stream.write(PersistAtoms.toByteArray(atoms));
         stream.close();
@@ -3587,6 +4402,41 @@
         return SipTransportSession.parseFrom(MessageNano.toByteArray(source));
     }
 
+    private static OutgoingShortCodeSms copyOf(OutgoingShortCodeSms source)
+            throws Exception {
+        return OutgoingShortCodeSms.parseFrom(MessageNano.toByteArray(source));
+    }
+
+    private static SatelliteController copyOf(SatelliteController source)
+            throws Exception {
+        return SatelliteController.parseFrom(MessageNano.toByteArray(source));
+    }
+
+    private static SatelliteSession copyOf(SatelliteSession source)
+            throws Exception {
+        return SatelliteSession.parseFrom(MessageNano.toByteArray(source));
+    }
+
+    private static SatelliteIncomingDatagram copyOf(SatelliteIncomingDatagram source)
+            throws Exception {
+        return SatelliteIncomingDatagram.parseFrom(MessageNano.toByteArray(source));
+    }
+
+    private static SatelliteOutgoingDatagram copyOf(SatelliteOutgoingDatagram source)
+            throws Exception {
+        return SatelliteOutgoingDatagram.parseFrom(MessageNano.toByteArray(source));
+    }
+
+    private static SatelliteProvision copyOf(SatelliteProvision source)
+            throws Exception {
+        return SatelliteProvision.parseFrom(MessageNano.toByteArray(source));
+    }
+
+    private static SatelliteSosMessageRecommender copyOf(SatelliteSosMessageRecommender source)
+            throws Exception {
+        return SatelliteSosMessageRecommender.parseFrom(MessageNano.toByteArray(source));
+    }
+
     private void assertAllPullTimestampEquals(long timestamp) {
         assertEquals(
                 timestamp,
@@ -3732,6 +4582,119 @@
         assertEquals(expectedCount, actualCount);
     }
 
+    private static void assertHasStats(
+            SatelliteController[] tested,
+            @Nullable SatelliteController expectedStats) {
+        assertNotNull(tested);
+        assertEquals(tested[0].countOfSatelliteServiceEnablementsSuccess,
+                expectedStats.countOfSatelliteServiceEnablementsSuccess);
+        assertEquals(tested[0].countOfSatelliteServiceEnablementsFail,
+                expectedStats.countOfSatelliteServiceEnablementsFail);
+        assertEquals(tested[0].countOfOutgoingDatagramSuccess,
+                expectedStats.countOfOutgoingDatagramSuccess);
+        assertEquals(tested[0].countOfOutgoingDatagramFail,
+                expectedStats.countOfOutgoingDatagramFail);
+        assertEquals(tested[0].countOfIncomingDatagramSuccess,
+                expectedStats.countOfIncomingDatagramSuccess);
+        assertEquals(tested[0].countOfIncomingDatagramFail,
+                expectedStats.countOfIncomingDatagramFail);
+        assertEquals(tested[0].countOfDatagramTypeSosSmsSuccess,
+                expectedStats.countOfDatagramTypeSosSmsSuccess);
+        assertEquals(tested[0].countOfDatagramTypeSosSmsFail,
+                expectedStats.countOfDatagramTypeSosSmsFail);
+        assertEquals(tested[0].countOfDatagramTypeLocationSharingSuccess,
+                expectedStats.countOfDatagramTypeLocationSharingSuccess);
+        assertEquals(tested[0].countOfDatagramTypeLocationSharingFail,
+                expectedStats.countOfDatagramTypeLocationSharingFail);
+        assertEquals(tested[0].totalServiceUptimeSec,
+                expectedStats.totalServiceUptimeSec);
+        assertEquals(tested[0].totalBatteryConsumptionPercent,
+                expectedStats.totalBatteryConsumptionPercent);
+        assertEquals(tested[0].totalBatteryChargedTimeSec,
+                expectedStats.totalBatteryChargedTimeSec);
+    }
+
+    private static void assertHasStatsAndCount(
+            SatelliteSession[] tested,
+            @Nullable SatelliteSession expectedStats, int expectedCount) {
+        assertNotNull(tested);
+        int actualCount = 0;
+        for (SatelliteSession stats : tested) {
+            if (stats.satelliteServiceInitializationResult
+                    == expectedStats.satelliteServiceInitializationResult
+                    && stats.satelliteTechnology == expectedStats.satelliteTechnology) {
+                actualCount = stats.count;
+            }
+        }
+        assertEquals(expectedCount, actualCount);
+    }
+
+    private static void assertHasStatsAndCount(
+            SatelliteIncomingDatagram[] tested,
+            @Nullable SatelliteIncomingDatagram expectedStats, int expectedCount) {
+        assertNotNull(tested);
+        int actualCount = 0;
+        for (SatelliteIncomingDatagram stats : tested) {
+            if (stats.resultCode == expectedStats.resultCode
+                    && stats.datagramSizeBytes == expectedStats.datagramSizeBytes
+                    && stats.datagramTransferTimeMillis
+                        == expectedStats.datagramTransferTimeMillis) {
+                actualCount++;
+            }
+        }
+        assertEquals(expectedCount, actualCount);
+    }
+
+    private static void assertHasStatsAndCount(
+            SatelliteOutgoingDatagram[] tested,
+            @Nullable SatelliteOutgoingDatagram expectedStats, int expectedCount) {
+        assertNotNull(tested);
+        int actualCount = 0;
+        for (SatelliteOutgoingDatagram stats : tested) {
+            if (stats.datagramType == expectedStats.datagramType
+                    && stats.resultCode == expectedStats.resultCode
+                    && stats.datagramSizeBytes == expectedStats.datagramSizeBytes
+                    && stats.datagramTransferTimeMillis
+                        == expectedStats.datagramTransferTimeMillis) {
+                actualCount++;
+            }
+        }
+        assertEquals(expectedCount, actualCount);
+    }
+
+    private static void assertHasStatsAndCount(
+            SatelliteProvision[] tested,
+            @Nullable SatelliteProvision expectedStats, int expectedCount) {
+        assertNotNull(tested);
+        int actualCount = 0;
+        for (SatelliteProvision stats : tested) {
+            if (stats.resultCode == expectedStats.resultCode
+                    && stats.provisioningTimeSec == expectedStats.provisioningTimeSec
+                    && stats.isProvisionRequest == expectedStats.isProvisionRequest
+                    && stats.isCanceled == expectedStats.isCanceled) {
+                actualCount++;
+            }
+        }
+        assertEquals(expectedCount, actualCount);
+    }
+
+    private static void assertHasStatsAndCount(
+            SatelliteSosMessageRecommender[] tested,
+            @Nullable SatelliteSosMessageRecommender expectedStats, int expectedCount) {
+        assertNotNull(tested);
+        int actualCount = 0;
+        for (SatelliteSosMessageRecommender stats : tested) {
+            if (stats.isDisplaySosMessageSent
+                    == expectedStats.isDisplaySosMessageSent
+                    && stats.countOfTimerStarted == expectedStats.countOfTimerStarted
+                    && stats.isImsRegistered == expectedStats.isImsRegistered
+                    && stats.cellularServiceState == expectedStats.cellularServiceState) {
+                actualCount = stats.count;
+            }
+        }
+        assertEquals(expectedCount, actualCount);
+    }
+
     private static void assertHasStatsAndCountDuration(
             RcsAcsProvisioningStats[] statses,
             @Nullable RcsAcsProvisioningStats expectedStats, int count, long duration) {
@@ -3866,4 +4829,18 @@
         }
         assertEquals(expectedCount, actualCount);
     }
+
+    private static void assertHasStatsAndCount(
+            OutgoingShortCodeSms[] outgoingShortCodeSmsList,
+            @Nullable OutgoingShortCodeSms expectedOutgoingShortCodeSms, int expectedCount) {
+        assertNotNull(outgoingShortCodeSmsList);
+        int actualCount = -1;
+        for (OutgoingShortCodeSms outgoingShortCodeSms : outgoingShortCodeSmsList) {
+            if (outgoingShortCodeSms.category == expectedOutgoingShortCodeSms.category
+                    && outgoingShortCodeSms.xmlVersion == expectedOutgoingShortCodeSms.xmlVersion) {
+                actualCount = outgoingShortCodeSms.shortCodeSmsCount;
+            }
+        }
+        assertEquals(expectedCount, actualCount);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/SatelliteStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/SatelliteStatsTest.java
new file mode 100644
index 0000000..22032ae
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/SatelliteStatsTest.java
@@ -0,0 +1,242 @@
+/*
+ * 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.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.telephony.SatelliteProtoEnums;
+import android.telephony.TelephonyProtoEnums;
+
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteController;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteIncomingDatagram;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteOutgoingDatagram;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteProvision;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteSession;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteSosMessageRecommender;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+public class SatelliteStatsTest extends TelephonyTest {
+    private static final String TAG = SatelliteStatsTest.class.getSimpleName();
+
+    private TestableSatelliteStats mSatelliteStats;
+
+    private class TestableSatelliteStats extends SatelliteStats {
+        TestableSatelliteStats() {
+            super();
+        }
+    }
+
+    @Before
+    public void setup() throws Exception {
+        super.setUp(getClass().getSimpleName());
+
+        mSatelliteStats = new TestableSatelliteStats();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mSatelliteStats = null;
+        super.tearDown();
+    }
+
+    @Test
+    public void onSatelliteControllerMetrics_withAtoms() throws Exception {
+        SatelliteStats.SatelliteControllerParams param =
+                new SatelliteStats.SatelliteControllerParams.Builder()
+                        .setCountOfSatelliteServiceEnablementsSuccess(2)
+                        .setCountOfSatelliteServiceEnablementsFail(2)
+                        .setCountOfOutgoingDatagramSuccess(8)
+                        .setCountOfOutgoingDatagramFail(9)
+                        .setCountOfIncomingDatagramSuccess(10)
+                        .setCountOfIncomingDatagramFail(11)
+                        .setCountOfDatagramTypeSosSmsSuccess(5)
+                        .setCountOfDatagramTypeSosSmsFail(5)
+                        .setCountOfDatagramTypeLocationSharingSuccess(6)
+                        .setCountOfDatagramTypeLocationSharingFail(6)
+                        .setCountOfProvisionSuccess(3)
+                        .setCountOfProvisionFail(4)
+                        .setCountOfDeprovisionSuccess(5)
+                        .setCountOfDeprovisionFail(6)
+                        .setTotalServiceUptimeSec(60 * 60 * 24 * 7)
+                        .setTotalBatteryConsumptionPercent(7)
+                        .setTotalBatteryChargedTimeSec(60 * 60 * 3)
+                        .build();
+
+        mSatelliteStats.onSatelliteControllerMetrics(param);
+
+        ArgumentCaptor<SatelliteController> captor =
+                ArgumentCaptor.forClass(SatelliteController.class);
+        verify(mPersistAtomsStorage).addSatelliteControllerStats(captor.capture());
+        SatelliteController stats = captor.getValue();
+        assertEquals(param.getCountOfSatelliteServiceEnablementsSuccess(),
+                stats.countOfSatelliteServiceEnablementsSuccess);
+        assertEquals(param.getCountOfSatelliteServiceEnablementsFail(),
+                stats.countOfSatelliteServiceEnablementsFail);
+        assertEquals(param.getCountOfOutgoingDatagramSuccess(),
+                stats.countOfOutgoingDatagramSuccess);
+        assertEquals(param.getCountOfOutgoingDatagramFail(),
+                stats.countOfOutgoingDatagramFail);
+        assertEquals(param.getCountOfIncomingDatagramSuccess(),
+                stats.countOfIncomingDatagramSuccess);
+        assertEquals(param.getCountOfIncomingDatagramFail(),
+                stats.countOfIncomingDatagramFail);
+        assertEquals(param.getCountOfDatagramTypeSosSmsSuccess(),
+                stats.countOfDatagramTypeSosSmsSuccess);
+        assertEquals(param.getCountOfDatagramTypeSosSmsFail(),
+                stats.countOfDatagramTypeSosSmsFail);
+        assertEquals(param.getCountOfDatagramTypeLocationSharingSuccess(),
+                stats.countOfDatagramTypeLocationSharingSuccess);
+        assertEquals(param.getCountOfDatagramTypeLocationSharingFail(),
+                stats.countOfDatagramTypeLocationSharingFail);
+        assertEquals(param.getCountOfProvisionSuccess(),
+                stats.countOfProvisionSuccess);
+        assertEquals(param.getCountOfProvisionFail(),
+                stats.countOfProvisionFail);
+        assertEquals(param.getCountOfDeprovisionSuccess(),
+                stats.countOfDeprovisionSuccess);
+        assertEquals(param.getCountOfDeprovisionFail(),
+                stats.countOfDeprovisionFail);
+        assertEquals(param.getTotalServiceUptimeSec(),
+                stats.totalServiceUptimeSec);
+        assertEquals(param.getTotalBatteryConsumptionPercent(),
+                stats.totalBatteryConsumptionPercent);
+        assertEquals(param.getTotalBatteryChargedTimeSec(),
+                stats.totalBatteryChargedTimeSec);
+
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    public void onSatelliteSessionMetrics_withAtoms() throws Exception {
+        SatelliteStats.SatelliteSessionParams param =
+                new SatelliteStats.SatelliteSessionParams.Builder()
+                        .setSatelliteServiceInitializationResult(
+                                SatelliteProtoEnums.SATELLITE_ERROR_NONE)
+                        .setSatelliteTechnology(SatelliteProtoEnums.NT_RADIO_TECHNOLOGY_PROPRIETARY)
+                        .build();
+
+        mSatelliteStats.onSatelliteSessionMetrics(param);
+
+        ArgumentCaptor<SatelliteSession> captor =
+                ArgumentCaptor.forClass(SatelliteSession.class);
+        verify(mPersistAtomsStorage).addSatelliteSessionStats(captor.capture());
+        SatelliteSession stats = captor.getValue();
+        assertEquals(param.getSatelliteServiceInitializationResult(),
+                stats.satelliteServiceInitializationResult);
+        assertEquals(param.getSatelliteTechnology(), stats.satelliteTechnology);
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    public void onSatelliteIncomingDatagramMetrics_withAtoms() throws Exception {
+        SatelliteStats.SatelliteIncomingDatagramParams param =
+                new SatelliteStats.SatelliteIncomingDatagramParams.Builder()
+                        .setResultCode(SatelliteProtoEnums.SATELLITE_ERROR_NONE)
+                        .setDatagramSizeBytes(1 * 1024)
+                        .setDatagramTransferTimeMillis(3 * 1000)
+                        .build();
+
+        mSatelliteStats.onSatelliteIncomingDatagramMetrics(param);
+
+        ArgumentCaptor<SatelliteIncomingDatagram> captor =
+                ArgumentCaptor.forClass(SatelliteIncomingDatagram.class);
+        verify(mPersistAtomsStorage).addSatelliteIncomingDatagramStats(captor.capture());
+        SatelliteIncomingDatagram stats = captor.getValue();
+        assertEquals(param.getResultCode(), stats.resultCode);
+        assertEquals(param.getDatagramSizeBytes(), stats.datagramSizeBytes);
+        assertEquals(param.getDatagramTransferTimeMillis(), stats.datagramTransferTimeMillis);
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    public void onSatelliteOutgoingDatagramMetrics_withAtoms() throws Exception {
+        SatelliteStats.SatelliteOutgoingDatagramParams param =
+                new SatelliteStats.SatelliteOutgoingDatagramParams.Builder()
+                        .setDatagramType(SatelliteProtoEnums.DATAGRAM_TYPE_LOCATION_SHARING)
+                        .setResultCode(SatelliteProtoEnums.SATELLITE_ERROR_NONE)
+                        .setDatagramSizeBytes(1 * 1024)
+                        .setDatagramTransferTimeMillis(3 * 1000)
+                        .build();
+
+        mSatelliteStats.onSatelliteOutgoingDatagramMetrics(param);
+
+        ArgumentCaptor<SatelliteOutgoingDatagram> captor =
+                ArgumentCaptor.forClass(SatelliteOutgoingDatagram.class);
+        verify(mPersistAtomsStorage).addSatelliteOutgoingDatagramStats(captor.capture());
+        SatelliteOutgoingDatagram stats = captor.getValue();
+        assertEquals(param.getDatagramType(), stats.datagramType);
+        assertEquals(param.getResultCode(), stats.resultCode);
+        assertEquals(param.getDatagramSizeBytes(), stats.datagramSizeBytes);
+        assertEquals(param.getDatagramTransferTimeMillis(), stats.datagramTransferTimeMillis);
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    public void onSatelliteProvisionMetrics_withAtoms() throws Exception {
+        SatelliteStats.SatelliteProvisionParams param =
+                new SatelliteStats.SatelliteProvisionParams.Builder()
+                        .setResultCode(SatelliteProtoEnums.SATELLITE_SERVICE_PROVISION_IN_PROGRESS)
+                        .setProvisioningTimeSec(5 * 1000)
+                        .setIsProvisionRequest(true)
+                        .setIsCanceled(false)
+                        .build();
+
+        mSatelliteStats.onSatelliteProvisionMetrics(param);
+
+        ArgumentCaptor<SatelliteProvision> captor =
+                ArgumentCaptor.forClass(SatelliteProvision.class);
+        verify(mPersistAtomsStorage).addSatelliteProvisionStats(captor.capture());
+        SatelliteProvision stats = captor.getValue();
+        assertEquals(param.getResultCode(), stats.resultCode);
+        assertEquals(param.getProvisioningTimeSec(), stats.provisioningTimeSec);
+        assertEquals(param.getIsProvisionRequest(), stats.isProvisionRequest);
+        assertEquals(param.getIsCanceled(), stats.isCanceled);
+
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    public void onSatelliteSosMessageRecommenderMetrics_withAtoms() throws Exception {
+        SatelliteStats.SatelliteSosMessageRecommenderParams param =
+                new SatelliteStats.SatelliteSosMessageRecommenderParams.Builder()
+                        .setDisplaySosMessageSent(true)
+                        .setCountOfTimerStarted(5)
+                        .setImsRegistered(false)
+                        .setCellularServiceState(TelephonyProtoEnums.SERVICE_STATE_OUT_OF_SERVICE)
+                        .build();
+
+        mSatelliteStats.onSatelliteSosMessageRecommender(param);
+
+        ArgumentCaptor<SatelliteSosMessageRecommender> captor =
+                ArgumentCaptor.forClass(SatelliteSosMessageRecommender.class);
+        verify(mPersistAtomsStorage).addSatelliteSosMessageRecommenderStats(captor.capture());
+        SatelliteSosMessageRecommender stats = captor.getValue();
+        assertEquals(param.isDisplaySosMessageSent(),
+                stats.isDisplaySosMessageSent);
+        assertEquals(param.getCountOfTimerStarted(), stats.countOfTimerStarted);
+        assertEquals(param.isImsRegistered(), stats.isImsRegistered);
+        assertEquals(param.getCellularServiceState(), stats.cellularServiceState);
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/ServiceStateStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/ServiceStateStatsTest.java
index 8406bc5..7b66a52 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/ServiceStateStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/ServiceStateStatsTest.java
@@ -16,6 +16,15 @@
 
 package com.android.internal.telephony.metrics;
 
+import static android.telephony.TelephonyManager.DATA_CONNECTED;
+import static android.telephony.TelephonyManager.DATA_UNKNOWN;
+
+import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_CLOSED;
+import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_UNKNOWN;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_UNKNOWN;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotSame;
@@ -35,10 +44,6 @@
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
-import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
-import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_UNKNOWN;
-
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch;
@@ -109,6 +114,9 @@
         mockWwanPsRat(TelephonyManager.NETWORK_TYPE_LTE);
         doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mImsStats).getImsVoiceRadioTech();
 
+        doReturn(DATA_CONNECTED).when(mDataNetworkController).getInternetDataNetworkState();
+        doReturn(mDataNetworkController).when(mSecondPhone).getDataNetworkController();
+
         mServiceStateStats = new TestableServiceStateStats(mPhone);
     }
 
@@ -143,6 +151,7 @@
         assertEquals(CARRIER1_ID, state.carrierId);
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
+        assertEquals(true, state.isInternetPdnUp);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -155,6 +164,7 @@
         doReturn(TelephonyManager.NETWORK_TYPE_UNKNOWN).when(mServiceState).getDataNetworkType();
         mockWwanCsRat(TelephonyManager.NETWORK_TYPE_UNKNOWN);
         mockWwanPsRat(TelephonyManager.NETWORK_TYPE_UNKNOWN);
+        doReturn(DATA_UNKNOWN).when(mDataNetworkController).getInternetDataNetworkState();
         mServiceStateStats.onServiceStateChanged(mServiceState);
 
         mServiceStateStats.incTimeMillis(100L);
@@ -176,6 +186,7 @@
         assertEquals(CARRIER1_ID, state.carrierId);
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
+        assertEquals(false, state.isInternetPdnUp);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -221,6 +232,7 @@
         doReturn(CardState.CARDSTATE_ABSENT).when(mPhysicalSlot0).getCardState();
         mockLimitedService(TelephonyManager.NETWORK_TYPE_UMTS);
         doReturn(-1).when(mPhone).getCarrierId();
+        doReturn(DATA_UNKNOWN).when(mDataNetworkController).getInternetDataNetworkState();
         mServiceStateStats.onServiceStateChanged(mServiceState);
 
         mServiceStateStats.incTimeMillis(100L);
@@ -242,6 +254,7 @@
         assertEquals(-1, state.carrierId);
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(true, state.isEmergencyOnly);
+        assertEquals(false, state.isInternetPdnUp);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -256,6 +269,7 @@
         mockWwanCsRat(TelephonyManager.NETWORK_TYPE_UNKNOWN);
         mockWwanPsRat(TelephonyManager.NETWORK_TYPE_UNKNOWN);
         doReturn(-1).when(mPhone).getCarrierId();
+        doReturn(DATA_UNKNOWN).when(mDataNetworkController).getInternetDataNetworkState();
         mServiceStateStats.onServiceStateChanged(mServiceState);
 
         mServiceStateStats.incTimeMillis(100L);
@@ -277,6 +291,7 @@
         assertEquals(-1, state.carrierId);
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
+        assertEquals(false, state.isInternetPdnUp);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -308,6 +323,7 @@
         assertEquals(CARRIER1_ID, state.carrierId);
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
+        assertEquals(true, state.isInternetPdnUp);
         state = captor.getAllValues().get(1);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat);
@@ -319,6 +335,7 @@
         assertEquals(CARRIER1_ID, state.carrierId);
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
+        assertEquals(true, state.isInternetPdnUp);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -355,6 +372,7 @@
         assertEquals(CARRIER1_ID, state.carrierId);
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
+        assertEquals(true, state.isInternetPdnUp);
         state = captor.getAllValues().get(1);
         assertEquals(TelephonyManager.NETWORK_TYPE_IWLAN, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_UNKNOWN, state.dataRat);
@@ -366,6 +384,50 @@
         assertEquals(CARRIER1_ID, state.carrierId);
         assertEquals(200L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
+        assertEquals(true, state.isInternetPdnUp);
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    @SmallTest
+    public void onInternetDataNetworkDisconnected() throws Exception {
+         // Using default service state for LTE
+        mServiceStateStats.onServiceStateChanged(mServiceState);
+
+        mServiceStateStats.incTimeMillis(100L);
+        mServiceStateStats.onInternetDataNetworkDisconnected();
+        mServiceStateStats.incTimeMillis(200L);
+        mServiceStateStats.conclude();
+
+        ArgumentCaptor<CellularServiceState> captor =
+                ArgumentCaptor.forClass(CellularServiceState.class);
+        verify(mPersistAtomsStorage, times(2))
+                .addCellularServiceStateAndCellularDataServiceSwitch(captor.capture(), eq(null));
+        assertNotSame(captor.getAllValues().get(0), captor.getAllValues().get(1));
+        CellularServiceState state = captor.getAllValues().get(0);
+        assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat);
+        assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat);
+        assertEquals(ServiceState.ROAMING_TYPE_NOT_ROAMING, state.voiceRoamingType);
+        assertEquals(ServiceState.ROAMING_TYPE_NOT_ROAMING, state.dataRoamingType);
+        assertFalse(state.isEndc);
+        assertEquals(0, state.simSlotIndex);
+        assertFalse(state.isMultiSim);
+        assertEquals(CARRIER1_ID, state.carrierId);
+        assertEquals(100L, state.totalTimeMillis);
+        assertEquals(false, state.isEmergencyOnly);
+        assertEquals(true, state.isInternetPdnUp);
+        state = captor.getAllValues().get(1);
+        assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat);
+        assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat);
+        assertEquals(ServiceState.ROAMING_TYPE_NOT_ROAMING, state.voiceRoamingType);
+        assertEquals(ServiceState.ROAMING_TYPE_NOT_ROAMING, state.dataRoamingType);
+        assertFalse(state.isEndc);
+        assertEquals(0, state.simSlotIndex);
+        assertFalse(state.isMultiSim);
+        assertEquals(CARRIER1_ID, state.carrierId);
+        assertEquals(200L, state.totalTimeMillis);
+        assertEquals(false, state.isEmergencyOnly);
+        assertEquals(false, state.isInternetPdnUp);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -394,6 +456,7 @@
         assertEquals(CARRIER1_ID, state.carrierId);
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
+        assertEquals(true, state.isInternetPdnUp);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -431,6 +494,7 @@
         assertEquals(CARRIER1_ID, state.carrierId);
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
+        assertEquals(true, state.isInternetPdnUp);
         state = serviceStateCaptor.getAllValues().get(1);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat);
@@ -442,6 +506,7 @@
         assertEquals(CARRIER1_ID, state.carrierId);
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
+        assertEquals(true, state.isInternetPdnUp);
         CellularDataServiceSwitch serviceSwitch = serviceSwitchCaptor.getAllValues().get(0);
         assertEquals(TelephonyManager.NETWORK_TYPE_UNKNOWN, serviceSwitch.ratFrom);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, serviceSwitch.ratTo);
@@ -482,6 +547,7 @@
         assertFalse(state.isMultiSim);
         assertEquals(CARRIER1_ID, state.carrierId);
         assertEquals(100L, state.totalTimeMillis);
+        assertEquals(true, state.isInternetPdnUp);
         state = captor.getAllValues().get(1);
         assertEquals(TelephonyManager.NETWORK_TYPE_IWLAN, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat);
@@ -492,6 +558,7 @@
         assertFalse(state.isMultiSim);
         assertEquals(CARRIER1_ID, state.carrierId);
         assertEquals(100L, state.totalTimeMillis);
+        assertEquals(true, state.isInternetPdnUp);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -520,6 +587,7 @@
         assertEquals(CARRIER1_ID, state.carrierId);
         assertEquals(0L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
+        assertEquals(true, state.isInternetPdnUp);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -560,6 +628,7 @@
         assertEquals(CARRIER1_ID, state.carrierId);
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
+        assertEquals(true, state.isInternetPdnUp);
         state = captor.getAllValues().get(1);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat);
@@ -571,6 +640,7 @@
         assertEquals(CARRIER1_ID, state.carrierId);
         assertEquals(200L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
+        assertEquals(true, state.isInternetPdnUp);
         state = captor.getAllValues().get(2);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat);
@@ -582,6 +652,7 @@
         assertEquals(CARRIER1_ID, state.carrierId);
         assertEquals(400L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
+        assertEquals(true, state.isInternetPdnUp);
         state = captor.getAllValues().get(3);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat);
@@ -593,6 +664,7 @@
         assertEquals(CARRIER1_ID, state.carrierId);
         assertEquals(800L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
+        assertEquals(true, state.isInternetPdnUp);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -637,6 +709,7 @@
         assertEquals(CARRIER1_ID, state.carrierId);
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
+        assertEquals(true, state.isInternetPdnUp);
         state = captor.getAllValues().get(1);
         assertEquals(TelephonyManager.NETWORK_TYPE_UNKNOWN, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_UNKNOWN, state.dataRat);
@@ -648,6 +721,7 @@
         assertEquals(-1, state.carrierId);
         assertEquals(5000L, state.totalTimeMillis);
         assertEquals(true, state.isEmergencyOnly);
+        assertEquals(true, state.isInternetPdnUp);
         state = captor.getAllValues().get(2);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat);
@@ -659,6 +733,7 @@
         assertEquals(CARRIER2_ID, state.carrierId);
         assertEquals(200L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
+        assertEquals(true, state.isInternetPdnUp);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -703,6 +778,7 @@
         assertEquals(CARRIER1_ID, state.carrierId);
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
+        assertEquals(true, state.isInternetPdnUp);
         state = serviceStateCaptor.getAllValues().get(1);
         assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.dataRat);
@@ -714,6 +790,7 @@
         assertEquals(CARRIER1_ID, state.carrierId);
         assertEquals(200L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
+        assertEquals(true, state.isInternetPdnUp);
         state = serviceStateCaptor.getAllValues().get(2);
         assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.dataRat);
@@ -725,6 +802,7 @@
         assertEquals(CARRIER1_ID, state.carrierId);
         assertEquals(400L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
+        assertEquals(true, state.isInternetPdnUp);
         CellularDataServiceSwitch serviceSwitch = serviceSwitchCaptor.getAllValues().get(0);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, serviceSwitch.ratFrom);
         assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, serviceSwitch.ratTo);
@@ -781,6 +859,7 @@
         assertEquals(CARRIER1_ID, state.carrierId);
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
+        assertEquals(true, state.isInternetPdnUp);
         state = serviceStateCaptor.getAllValues().get(1);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat);
@@ -792,6 +871,7 @@
         assertEquals(CARRIER1_ID, state.carrierId);
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
+        assertEquals(true, state.isInternetPdnUp);
         state = serviceStateCaptor.getAllValues().get(2);
         assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.dataRat);
@@ -803,6 +883,7 @@
         assertEquals(CARRIER1_ID, state.carrierId);
         assertEquals(200L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
+        assertEquals(true, state.isInternetPdnUp);
         state = serviceStateCaptor.getAllValues().get(3);
         assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, state.dataRat);
@@ -814,6 +895,7 @@
         assertEquals(CARRIER1_ID, state.carrierId);
         assertEquals(200L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
+        assertEquals(true, state.isInternetPdnUp);
         CellularDataServiceSwitch serviceSwitch = serviceSwitchCaptor.getAllValues().get(0);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, serviceSwitch.ratFrom);
         assertEquals(TelephonyManager.NETWORK_TYPE_UMTS, serviceSwitch.ratTo);
@@ -874,6 +956,7 @@
         assertEquals(CARRIER1_ID, state.carrierId);
         assertEquals(100L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
+        assertEquals(true, state.isInternetPdnUp);
         state = captor.getAllValues().get(1);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.voiceRat);
         assertEquals(TelephonyManager.NETWORK_TYPE_LTE, state.dataRat);
@@ -885,6 +968,7 @@
         assertEquals(CARRIER1_ID, state.carrierId);
         assertEquals(200L, state.totalTimeMillis);
         assertEquals(false, state.isEmergencyOnly);
+        assertEquals(true, state.isInternetPdnUp);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -911,6 +995,47 @@
                 mPhone, mServiceState, VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_UNKNOWN));
     }
 
+    @Test
+    @SmallTest
+    public void onFoldStateChanged_modemOff() throws Exception {
+        doReturn(ServiceState.STATE_POWER_OFF).when(mServiceState).getVoiceRegState();
+        doReturn(ServiceState.STATE_POWER_OFF).when(mServiceState).getDataRegState();
+        doReturn(TelephonyManager.NETWORK_TYPE_UNKNOWN).when(mServiceState).getVoiceNetworkType();
+        doReturn(TelephonyManager.NETWORK_TYPE_UNKNOWN).when(mServiceState).getDataNetworkType();
+        mockWwanPsRat(TelephonyManager.NETWORK_TYPE_UNKNOWN);
+        doReturn(-1).when(mPhone).getCarrierId();
+        mServiceStateStats.onServiceStateChanged(mServiceState);
+        mServiceStateStats.incTimeMillis(100L);
+
+        mServiceStateStats.onFoldStateChanged(CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_CLOSED);
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    @SmallTest
+    public void onFoldStateChanged_LTEMode() throws Exception {
+        // Using default service state for LTE with fold state unknown
+        mServiceStateStats.onServiceStateChanged(mServiceState);
+        mServiceStateStats.incTimeMillis(100L);
+        mServiceStateStats.onFoldStateChanged(CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_CLOSED);
+        mServiceStateStats.incTimeMillis(1000L);
+        // Same fold state as before should not generate a new atom
+        mServiceStateStats.onFoldStateChanged(CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_CLOSED);
+        mServiceStateStats.incTimeMillis(1000L);
+
+        // There should be 2 service state updates
+        mServiceStateStats.conclude();
+        ArgumentCaptor<CellularServiceState> captor =
+                ArgumentCaptor.forClass(CellularServiceState.class);
+        verify(mPersistAtomsStorage, times(2))
+                .addCellularServiceStateAndCellularDataServiceSwitch(captor.capture(), eq(null));
+        CellularServiceState state = captor.getAllValues().get(0);
+        assertEquals(CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_UNKNOWN, state.foldState);
+        state = captor.getAllValues().get(1);
+        assertEquals(CELLULAR_SERVICE_STATE__FOLD_STATE__STATE_CLOSED, state.foldState);
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
     private void mockWwanPsRat(@NetworkType int rat) {
         mockWwanRat(
                 NetworkRegistrationInfo.DOMAIN_PS,
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java
index fb556e6..2ca0b16 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java
@@ -77,7 +77,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
@@ -927,7 +926,6 @@
 
     @Test
     @SmallTest
-    @Ignore("b/256234604")
     public void singleImsCall_ratSwitchToUnknown() {
         setServiceState(mServiceState, TelephonyManager.NETWORK_TYPE_LTE);
         doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mImsStats).getImsVoiceRadioTech();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineImplTest.java b/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineImplTest.java
index 2ac0c98..580d533 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineImplTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzStateMachineImplTest.java
@@ -910,7 +910,8 @@
             suggestedTimes.set(timeSuggestion);
             if (timeSuggestion.getUnixEpochTime() != null) {
                 // The fake time service just uses the latest suggestion.
-                mFakeDeviceState.currentTimeMillis = timeSuggestion.getUnixEpochTime().getValue();
+                mFakeDeviceState.currentTimeMillis =
+                        timeSuggestion.getUnixEpochTime().getUnixEpochTimeMillis();
             }
         }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/AntennaDirectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/AntennaDirectionTest.java
new file mode 100644
index 0000000..6a91d95
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/AntennaDirectionTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.satellite;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.os.Parcel;
+import android.telephony.satellite.AntennaDirection;
+
+import org.junit.Test;
+
+public class AntennaDirectionTest {
+
+    @Test
+    public void testParcel() {
+        AntennaDirection antennaDirection = new AntennaDirection(1, 2, 3);
+
+        Parcel p = Parcel.obtain();
+        antennaDirection.writeToParcel(p, 0);
+        p.setDataPosition(0);
+
+        AntennaDirection fromParcel = AntennaDirection.CREATOR.createFromParcel(p);
+        assertThat(antennaDirection).isEqualTo(fromParcel);
+    }
+
+    @Test
+    public void testEquals() {
+        AntennaDirection antennaDirection1 = new AntennaDirection(1.12f, 1.13f, 1.14f);
+        AntennaDirection antennaDirection2 = new AntennaDirection(1.12f, 1.13f, 1.14f);
+        assertEquals(antennaDirection1, antennaDirection2);
+
+        AntennaDirection antennaDirection3 = new AntennaDirection(1.121f, 1.131f, 1.141f);
+        assertNotEquals(antennaDirection1, antennaDirection3);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/AntennaPositionTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/AntennaPositionTest.java
new file mode 100644
index 0000000..919ab83
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/AntennaPositionTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.satellite;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.os.Parcel;
+import android.telephony.satellite.AntennaDirection;
+import android.telephony.satellite.AntennaPosition;
+import android.telephony.satellite.SatelliteManager;
+
+import org.junit.Test;
+
+public class AntennaPositionTest {
+
+    private AntennaDirection mAntennaDirection = new AntennaDirection(1,1,1);
+
+    @Test
+    public void testParcel() {
+        AntennaPosition antennaPosition = new AntennaPosition(mAntennaDirection,
+                SatelliteManager.DEVICE_HOLD_POSITION_PORTRAIT);
+
+        Parcel p = Parcel.obtain();
+        antennaPosition.writeToParcel(p, 0);
+        p.setDataPosition(0);
+
+        AntennaPosition fromParcel = AntennaPosition.CREATOR.createFromParcel(p);
+        assertThat(antennaPosition).isEqualTo(fromParcel);
+    }
+
+    @Test
+    public void testEquals() {
+        AntennaPosition antennaPosition1 = new AntennaPosition(mAntennaDirection,
+                SatelliteManager.DEVICE_HOLD_POSITION_PORTRAIT);
+        AntennaPosition antennaPosition2 = new AntennaPosition(mAntennaDirection,
+                SatelliteManager.DEVICE_HOLD_POSITION_PORTRAIT);
+        assertEquals(antennaPosition1, antennaPosition2);
+
+        AntennaPosition antennaPosition3 = new AntennaPosition(mAntennaDirection,
+                SatelliteManager.DEVICE_HOLD_POSITION_LANDSCAPE_LEFT);
+        assertNotEquals(antennaPosition1, antennaPosition3);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/ControllerMetricsStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/ControllerMetricsStatsTest.java
new file mode 100644
index 0000000..baa00c1
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/ControllerMetricsStatsTest.java
@@ -0,0 +1,607 @@
+/*
+ * 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.satellite;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.telephony.satellite.SatelliteManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.metrics.SatelliteStats;
+import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats;
+import com.android.telephony.Rlog;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class ControllerMetricsStatsTest extends TelephonyTest {
+    private static final String TAG = "ControllerMetricsStatsTest";
+
+    private static final long MODEM_ENABLED_TIME = 1000L;
+
+    private TestControllerMetricsStats mControllerMetricsStatsUT;
+    private TestSatelliteStats mTestStats;
+
+    @Mock private Context mMockContext;
+    @Spy private ControllerMetricsStats mSpyControllerMetricsStats;
+    @Mock private SatelliteStats mMockSatelliteStats;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        Rlog.d(TAG, "setUp()");
+        mTestStats = new TestSatelliteStats();
+        mControllerMetricsStatsUT =
+                new TestControllerMetricsStats(mMockContext, mTestStats);
+        mMockContext = mock(Context.class);
+        mMockSatelliteStats = mock(SatelliteStats.class);
+        mSpyControllerMetricsStats =
+                Mockito.spy(ControllerMetricsStats.make(mMockContext, mMockSatelliteStats));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        Rlog.d(TAG, "tearDown()");
+        mTestStats = null;
+        mControllerMetricsStatsUT = null;
+        mMockSatelliteStats = null;
+        mSpyControllerMetricsStats = null;
+        super.tearDown();
+    }
+
+    @Test
+    public void testReportServiceEnablementSuccessCount() {
+        mTestStats.initializeParams();
+        for (int i = 0; i < 10; i++) {
+            mControllerMetricsStatsUT.reportServiceEnablementSuccessCount();
+        }
+        assertEquals(10, mTestStats.mCountOfSatelliteServiceEnablementsSuccess);
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail);
+        assertEquals(0, mTestStats.mCountOfProvisionSuccess);
+        assertEquals(0, mTestStats.mCountOfProvisionFail);
+        assertEquals(0, mTestStats.mCountOfDeprovisionSuccess);
+        assertEquals(0, mTestStats.mCountOfDeprovisionFail);
+        assertEquals(0, mTestStats.mTotalServiceUptimeSec);
+        assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent);
+        assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
+        mTestStats.initializeParams();
+    }
+
+    @Test
+    public void testReportServiceEnablementFailCount() {
+        mTestStats.initializeParams();
+        for (int i = 0; i < 10; i++) {
+            mControllerMetricsStatsUT.reportServiceEnablementFailCount();
+        }
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess);
+        assertEquals(10, mTestStats.mCountOfSatelliteServiceEnablementsFail);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail);
+        assertEquals(0, mTestStats.mCountOfProvisionSuccess);
+        assertEquals(0, mTestStats.mCountOfProvisionFail);
+        assertEquals(0, mTestStats.mCountOfDeprovisionSuccess);
+        assertEquals(0, mTestStats.mCountOfDeprovisionFail);
+        assertEquals(0, mTestStats.mTotalServiceUptimeSec);
+        assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent);
+        assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
+        mTestStats.initializeParams();
+    }
+
+    @Test
+    public void testReportOutgoingDatagramSuccessCount() {
+        mTestStats.initializeParams();
+        int datagramType = SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE;
+        for (int i = 0; i < 10; i++) {
+            mControllerMetricsStatsUT.reportOutgoingDatagramSuccessCount(datagramType);
+        }
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess);
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail);
+        assertEquals(10, mTestStats.mCountOfOutgoingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramFail);
+        assertEquals(10, mTestStats.mCountOfDatagramTypeSosSmsSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail);
+        assertEquals(0, mTestStats.mCountOfProvisionSuccess);
+        assertEquals(0, mTestStats.mCountOfProvisionFail);
+        assertEquals(0, mTestStats.mCountOfDeprovisionSuccess);
+        assertEquals(0, mTestStats.mCountOfDeprovisionFail);
+        assertEquals(0, mTestStats.mTotalServiceUptimeSec);
+        assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent);
+        assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
+        mTestStats.initializeParams();
+
+        datagramType = SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING;
+        for (int i = 0; i < 10; i++) {
+            mControllerMetricsStatsUT.reportOutgoingDatagramSuccessCount(datagramType);
+        }
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess);
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail);
+        assertEquals(10, mTestStats.mCountOfOutgoingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail);
+        assertEquals(10, mTestStats.mCountOfDatagramTypeLocationSharingSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail);
+        assertEquals(0, mTestStats.mCountOfProvisionSuccess);
+        assertEquals(0, mTestStats.mCountOfProvisionFail);
+        assertEquals(0, mTestStats.mCountOfDeprovisionSuccess);
+        assertEquals(0, mTestStats.mCountOfDeprovisionFail);
+        assertEquals(0, mTestStats.mTotalServiceUptimeSec);
+        assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent);
+        assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
+        mTestStats.initializeParams();
+
+        datagramType = SatelliteManager.DATAGRAM_TYPE_UNKNOWN;
+        for (int i = 0; i < 10; i++) {
+            mControllerMetricsStatsUT.reportOutgoingDatagramSuccessCount(datagramType);
+        }
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess);
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail);
+        assertEquals(10, mTestStats.mCountOfOutgoingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail);
+        assertEquals(0, mTestStats.mCountOfProvisionSuccess);
+        assertEquals(0, mTestStats.mCountOfProvisionFail);
+        assertEquals(0, mTestStats.mCountOfDeprovisionSuccess);
+        assertEquals(0, mTestStats.mCountOfDeprovisionFail);
+        assertEquals(0, mTestStats.mTotalServiceUptimeSec);
+        assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent);
+        assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
+        mTestStats.initializeParams();
+    }
+
+    @Test
+    public void reportOutgoingDatagramFailCount() {
+        mTestStats.initializeParams();
+        int datagramType = SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE;
+        for (int i = 0; i < 10; i++) {
+            mControllerMetricsStatsUT.reportOutgoingDatagramFailCount(datagramType);
+        }
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess);
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess);
+        assertEquals(10, mTestStats.mCountOfOutgoingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess);
+        assertEquals(10, mTestStats.mCountOfDatagramTypeSosSmsFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail);
+        assertEquals(0, mTestStats.mCountOfProvisionSuccess);
+        assertEquals(0, mTestStats.mCountOfProvisionFail);
+        assertEquals(0, mTestStats.mCountOfDeprovisionSuccess);
+        assertEquals(0, mTestStats.mCountOfDeprovisionFail);
+        assertEquals(0, mTestStats.mTotalServiceUptimeSec);
+        assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent);
+        assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
+        mTestStats.initializeParams();
+
+        datagramType = SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING;
+        for (int i = 0; i < 10; i++) {
+            mControllerMetricsStatsUT.reportOutgoingDatagramFailCount(datagramType);
+        }
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess);
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess);
+        assertEquals(10, mTestStats.mCountOfOutgoingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess);
+        assertEquals(10, mTestStats.mCountOfDatagramTypeLocationSharingFail);
+        assertEquals(0, mTestStats.mCountOfProvisionSuccess);
+        assertEquals(0, mTestStats.mCountOfProvisionFail);
+        assertEquals(0, mTestStats.mCountOfDeprovisionSuccess);
+        assertEquals(0, mTestStats.mCountOfDeprovisionFail);
+        assertEquals(0, mTestStats.mTotalServiceUptimeSec);
+        assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent);
+        assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
+        mTestStats.initializeParams();
+
+        datagramType = SatelliteManager.DATAGRAM_TYPE_UNKNOWN;
+        for (int i = 0; i < 10; i++) {
+            mControllerMetricsStatsUT.reportOutgoingDatagramFailCount(datagramType);
+        }
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess);
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess);
+        assertEquals(10, mTestStats.mCountOfOutgoingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail);
+        assertEquals(0, mTestStats.mCountOfProvisionSuccess);
+        assertEquals(0, mTestStats.mCountOfProvisionFail);
+        assertEquals(0, mTestStats.mCountOfDeprovisionSuccess);
+        assertEquals(0, mTestStats.mCountOfDeprovisionFail);
+        assertEquals(0, mTestStats.mTotalServiceUptimeSec);
+        assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent);
+        assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
+        mTestStats.initializeParams();
+    }
+
+    @Test
+    public void testReportIncomingDatagramCount() {
+        mTestStats.initializeParams();
+
+        int result = SatelliteManager.SATELLITE_ERROR_NONE;
+        for (int i = 0; i < 10; i++) {
+            mControllerMetricsStatsUT.reportIncomingDatagramCount(result);
+        }
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess);
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail);
+        assertEquals(10, mTestStats.mCountOfIncomingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail);
+        assertEquals(0, mTestStats.mCountOfProvisionSuccess);
+        assertEquals(0, mTestStats.mCountOfProvisionFail);
+        assertEquals(0, mTestStats.mCountOfDeprovisionSuccess);
+        assertEquals(0, mTestStats.mCountOfDeprovisionFail);
+        assertEquals(0, mTestStats.mTotalServiceUptimeSec);
+        assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent);
+        assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
+        mTestStats.initializeParams();
+
+        result = SatelliteManager.SATELLITE_SERVER_ERROR;
+        for (int i = 0; i < 10; i++) {
+            mControllerMetricsStatsUT.reportIncomingDatagramCount(result);
+        }
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess);
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess);
+        assertEquals(10, mTestStats.mCountOfIncomingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail);
+        assertEquals(0, mTestStats.mCountOfProvisionSuccess);
+        assertEquals(0, mTestStats.mCountOfProvisionFail);
+        assertEquals(0, mTestStats.mCountOfDeprovisionSuccess);
+        assertEquals(0, mTestStats.mCountOfDeprovisionFail);
+        assertEquals(0, mTestStats.mTotalServiceUptimeSec);
+        assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent);
+        assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
+        mTestStats.initializeParams();
+
+        result = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE;
+        for (int i = 0; i < 10; i++) {
+            mControllerMetricsStatsUT.reportIncomingDatagramCount(result);
+        }
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess);
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess);
+        assertEquals(10, mTestStats.mCountOfIncomingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail);
+        assertEquals(0, mTestStats.mCountOfProvisionSuccess);
+        assertEquals(0, mTestStats.mCountOfProvisionFail);
+        assertEquals(0, mTestStats.mCountOfDeprovisionSuccess);
+        assertEquals(0, mTestStats.mCountOfDeprovisionFail);
+        assertEquals(0, mTestStats.mTotalServiceUptimeSec);
+        assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent);
+        assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
+        mTestStats.initializeParams();
+    }
+
+    @Test
+    public void testReportProvisionCount() {
+        mTestStats.initializeParams();
+
+        int result = SatelliteManager.SATELLITE_ERROR_NONE;
+        for (int i = 0; i < 10; i++) {
+            mControllerMetricsStatsUT.reportProvisionCount(result);
+        }
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess);
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail);
+        assertEquals(10, mTestStats.mCountOfProvisionSuccess);
+        assertEquals(0, mTestStats.mCountOfProvisionFail);
+        assertEquals(0, mTestStats.mCountOfDeprovisionSuccess);
+        assertEquals(0, mTestStats.mCountOfDeprovisionFail);
+        assertEquals(0, mTestStats.mTotalServiceUptimeSec);
+        assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent);
+        assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
+        mTestStats.initializeParams();
+
+        result = SatelliteManager.SATELLITE_SERVER_ERROR;
+        for (int i = 0; i < 10; i++) {
+            mControllerMetricsStatsUT.reportProvisionCount(result);
+        }
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess);
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail);
+        assertEquals(0, mTestStats.mCountOfProvisionSuccess);
+        assertEquals(10, mTestStats.mCountOfProvisionFail);
+        assertEquals(0, mTestStats.mCountOfDeprovisionSuccess);
+        assertEquals(0, mTestStats.mCountOfDeprovisionFail);
+        assertEquals(0, mTestStats.mTotalServiceUptimeSec);
+        assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent);
+        assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
+        mTestStats.initializeParams();
+
+        result = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE;
+        for (int i = 0; i < 10; i++) {
+            mControllerMetricsStatsUT.reportProvisionCount(result);
+        }
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess);
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail);
+        assertEquals(0, mTestStats.mCountOfProvisionSuccess);
+        assertEquals(10, mTestStats.mCountOfProvisionFail);
+        assertEquals(0, mTestStats.mCountOfDeprovisionSuccess);
+        assertEquals(0, mTestStats.mCountOfDeprovisionFail);
+        assertEquals(0, mTestStats.mTotalServiceUptimeSec);
+        assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent);
+        assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
+        mTestStats.initializeParams();
+    }
+
+    @Test
+    public void testReportDeprovisionCount() {
+        mTestStats.initializeParams();
+
+        int result = SatelliteManager.SATELLITE_ERROR_NONE;
+        for (int i = 0; i < 10; i++) {
+            mControllerMetricsStatsUT.reportDeprovisionCount(result);
+        }
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess);
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail);
+        assertEquals(0, mTestStats.mCountOfProvisionSuccess);
+        assertEquals(0, mTestStats.mCountOfProvisionFail);
+        assertEquals(10, mTestStats.mCountOfDeprovisionSuccess);
+        assertEquals(0, mTestStats.mCountOfDeprovisionFail);
+        assertEquals(0, mTestStats.mTotalServiceUptimeSec);
+        assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent);
+        assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
+        mTestStats.initializeParams();
+
+        result = SatelliteManager.SATELLITE_SERVER_ERROR;
+        for (int i = 0; i < 10; i++) {
+            mControllerMetricsStatsUT.reportDeprovisionCount(result);
+        }
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess);
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail);
+        assertEquals(0, mTestStats.mCountOfProvisionSuccess);
+        assertEquals(0, mTestStats.mCountOfProvisionFail);
+        assertEquals(0, mTestStats.mCountOfDeprovisionSuccess);
+        assertEquals(10, mTestStats.mCountOfDeprovisionFail);
+        assertEquals(0, mTestStats.mTotalServiceUptimeSec);
+        assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent);
+        assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
+        mTestStats.initializeParams();
+
+        result = SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE;
+        for (int i = 0; i < 10; i++) {
+            mControllerMetricsStatsUT.reportDeprovisionCount(result);
+        }
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess);
+        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail);
+        assertEquals(0, mTestStats.mCountOfProvisionSuccess);
+        assertEquals(0, mTestStats.mCountOfProvisionFail);
+        assertEquals(0, mTestStats.mCountOfDeprovisionSuccess);
+        assertEquals(10, mTestStats.mCountOfDeprovisionFail);
+        assertEquals(0, mTestStats.mTotalServiceUptimeSec);
+        assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent);
+        assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
+        mTestStats.initializeParams();
+    }
+
+    @Test
+    public void testOnSatelliteEnabled() {
+        // set precondition
+        doReturn(false).when(mSpyControllerMetricsStats).isSatelliteModemOn();
+
+        doNothing().when(mSpyControllerMetricsStats).startCaptureBatteryLevel();
+        doReturn(MODEM_ENABLED_TIME).when(mSpyControllerMetricsStats).getCurrentTime();
+
+        // test object
+        mSpyControllerMetricsStats.onSatelliteEnabled();
+
+        // verification
+        verify(mSpyControllerMetricsStats).startCaptureBatteryLevel();
+        verify(mSpyControllerMetricsStats).getCurrentTime();
+    }
+
+    @Test
+    public void testOnSatelliteDisabled() {
+        // set precondition
+        doNothing().when(mMockSatelliteStats).onSatelliteControllerMetrics(any());
+
+        doReturn(true).when(mSpyControllerMetricsStats).isSatelliteModemOn();
+
+        doReturn(0).when(mSpyControllerMetricsStats).captureTotalServiceUpTimeSec();
+        doReturn(0).when(mSpyControllerMetricsStats).captureTotalBatteryConsumptionPercent(any());
+        doReturn(0).when(mSpyControllerMetricsStats).captureTotalBatteryChargeTimeSec();
+
+        // test object
+        mSpyControllerMetricsStats.onSatelliteDisabled();
+
+        // verification
+        verify(mSpyControllerMetricsStats).captureTotalServiceUpTimeSec();
+        verify(mSpyControllerMetricsStats).captureTotalBatteryConsumptionPercent(any());
+        verify(mSpyControllerMetricsStats).captureTotalBatteryChargeTimeSec();
+    }
+
+    static class TestControllerMetricsStats extends ControllerMetricsStats {
+        TestControllerMetricsStats(Context context, SatelliteStats satelliteStats) {
+            super(context, satelliteStats);
+        }
+    }
+
+    static class TestSatelliteStats extends SatelliteStats {
+        public int mCountOfSatelliteServiceEnablementsSuccess;
+        public int mCountOfSatelliteServiceEnablementsFail;
+        public int mCountOfOutgoingDatagramSuccess;
+        public int mCountOfOutgoingDatagramFail;
+        public int mCountOfIncomingDatagramSuccess;
+        public int mCountOfIncomingDatagramFail;
+        public int mCountOfDatagramTypeSosSmsSuccess;
+        public int mCountOfDatagramTypeSosSmsFail;
+        public int mCountOfDatagramTypeLocationSharingSuccess;
+        public int mCountOfDatagramTypeLocationSharingFail;
+        public int mCountOfProvisionSuccess;
+        public int mCountOfProvisionFail;
+        public int mCountOfDeprovisionSuccess;
+        public int mCountOfDeprovisionFail;
+        public int mTotalServiceUptimeSec;
+        public int mTotalBatteryConsumptionPercent;
+        public int mTotalBatteryChargedTimeSec;
+
+        @Override
+        public synchronized void onSatelliteControllerMetrics(SatelliteControllerParams param) {
+            mCountOfSatelliteServiceEnablementsSuccess +=
+                    param.getCountOfSatelliteServiceEnablementsSuccess();
+            mCountOfSatelliteServiceEnablementsFail +=
+                    param.getCountOfSatelliteServiceEnablementsFail();
+            mCountOfOutgoingDatagramSuccess += param.getCountOfOutgoingDatagramSuccess();
+            mCountOfOutgoingDatagramFail += param.getCountOfOutgoingDatagramFail();
+            mCountOfIncomingDatagramSuccess += param.getCountOfIncomingDatagramSuccess();
+            mCountOfIncomingDatagramFail += param.getCountOfIncomingDatagramFail();
+            mCountOfDatagramTypeSosSmsSuccess += param.getCountOfDatagramTypeSosSmsSuccess();
+            mCountOfDatagramTypeSosSmsFail += param.getCountOfDatagramTypeSosSmsFail();
+            mCountOfDatagramTypeLocationSharingSuccess +=
+                    param.getCountOfDatagramTypeLocationSharingSuccess();
+            mCountOfDatagramTypeLocationSharingFail +=
+                    param.getCountOfDatagramTypeLocationSharingFail();
+            mCountOfProvisionSuccess += param.getCountOfProvisionSuccess();
+            mCountOfProvisionFail += param.getCountOfProvisionFail();
+            mCountOfDeprovisionSuccess += param.getCountOfDeprovisionSuccess();
+            mCountOfDeprovisionFail += param.getCountOfDeprovisionFail();
+            mTotalServiceUptimeSec += param.getTotalServiceUptimeSec();
+            mTotalBatteryConsumptionPercent += param.getTotalBatteryConsumptionPercent();
+            mTotalBatteryChargedTimeSec += param.getTotalBatteryChargedTimeSec();
+        }
+
+        public void initializeParams() {
+            mCountOfSatelliteServiceEnablementsSuccess = 0;
+            mCountOfSatelliteServiceEnablementsFail = 0;
+            mCountOfOutgoingDatagramSuccess = 0;
+            mCountOfOutgoingDatagramFail = 0;
+            mCountOfIncomingDatagramSuccess = 0;
+            mCountOfIncomingDatagramFail = 0;
+            mCountOfDatagramTypeSosSmsSuccess = 0;
+            mCountOfDatagramTypeSosSmsFail = 0;
+            mCountOfDatagramTypeLocationSharingSuccess = 0;
+            mCountOfDatagramTypeLocationSharingFail = 0;
+            mCountOfProvisionSuccess = 0;
+            mCountOfProvisionFail = 0;
+            mCountOfDeprovisionSuccess = 0;
+            mCountOfDeprovisionFail = 0;
+            mTotalServiceUptimeSec = 0;
+            mTotalBatteryConsumptionPercent = 0;
+            mTotalBatteryChargedTimeSec = 0;
+        }
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java
new file mode 100644
index 0000000..bc1d767
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java
@@ -0,0 +1,499 @@
+/*
+ * 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.satellite;
+
+import static com.android.internal.telephony.satellite.DatagramController.SATELLITE_ALIGN_TIMEOUT;
+
+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.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.Looper;
+import android.os.Message;
+import android.telephony.satellite.SatelliteDatagram;
+import android.telephony.satellite.SatelliteManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class DatagramDispatcherTest extends TelephonyTest {
+    private static final String TAG = "DatagramDispatcherTest";
+    private static final int SUB_ID = 0;
+    private static final int DATAGRAM_TYPE1 = SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE;
+    private static final int DATAGRAM_TYPE2 = SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING;
+    private static final String TEST_MESSAGE = "This is a test datagram message";
+    private static final long TEST_EXPIRE_TIMER_SATELLITE_ALIGN = TimeUnit.SECONDS.toMillis(1);
+
+    private DatagramDispatcher mDatagramDispatcherUT;
+    private TestDatagramDispatcher mTestDemoModeDatagramDispatcher;
+
+    @Mock private DatagramController mMockDatagramController;
+    @Mock private DatagramReceiver mMockDatagramReceiver;
+    @Mock private SatelliteModemInterface mMockSatelliteModemInterface;
+    @Mock private ControllerMetricsStats mMockControllerMetricsStats;
+
+    /** Variables required to send datagram in the unit tests. */
+    LinkedBlockingQueue<Integer> mResultListener;
+    SatelliteDatagram mDatagram;
+    InOrder mInOrder;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        MockitoAnnotations.initMocks(this);
+        logd(TAG + " Setup!");
+
+        replaceInstance(DatagramController.class, "sInstance", null,
+                mMockDatagramController);
+        replaceInstance(DatagramReceiver.class, "sInstance", null,
+                mMockDatagramReceiver);
+        replaceInstance(SatelliteModemInterface.class, "sInstance", null,
+                mMockSatelliteModemInterface);
+        replaceInstance(ControllerMetricsStats.class, "sInstance", null,
+                mMockControllerMetricsStats);
+
+        mDatagramDispatcherUT = DatagramDispatcher.make(mContext, Looper.myLooper(),
+                mMockDatagramController);
+        mTestDemoModeDatagramDispatcher = new TestDatagramDispatcher(mContext, Looper.myLooper(),
+                mMockDatagramController);
+
+        mResultListener = new LinkedBlockingQueue<>(1);
+        mDatagram = new SatelliteDatagram(TEST_MESSAGE.getBytes());
+        mInOrder = inOrder(mMockDatagramController);
+        when(mMockDatagramController.isPollingInIdleState()).thenReturn(true);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        logd(TAG + " tearDown");
+        mDatagramDispatcherUT.destroy();
+        mDatagramDispatcherUT = null;
+        mTestDemoModeDatagramDispatcher = null;
+        mResultListener = null;
+        mDatagram = null;
+        mInOrder = null;
+        super.tearDown();
+    }
+
+    @Test
+    public void testSendSatelliteDatagram_usingSatelliteModemInterface_success() throws  Exception {
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[3];
+
+            mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
+                    new AsyncResult(message.obj, null, null))
+                    .sendToTarget();
+            return null;
+        }).when(mMockSatelliteModemInterface).sendSatelliteDatagram(any(SatelliteDatagram.class),
+                anyBoolean(), anyBoolean(), any(Message.class));
+
+        mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
+                true, mResultListener::offer);
+
+        processAllMessages();
+
+        mInOrder.verify(mMockDatagramController).isPollingInIdleState();
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
+                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS), eq(0),
+                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
+                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+        verifyNoMoreInteractions(mMockDatagramController);
+
+        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE);
+    }
+
+    @Test
+    public void testSendSatelliteDatagram_usingSatelliteModemInterface_failure() throws  Exception {
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[3];
+
+            mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
+                            new AsyncResult(message.obj, null,
+                                    new SatelliteManager.SatelliteException(
+                                            SatelliteManager.SATELLITE_SERVICE_ERROR)))
+                    .sendToTarget();
+            return null;
+        }).when(mMockSatelliteModemInterface).sendSatelliteDatagram(any(SatelliteDatagram.class),
+                anyBoolean(), anyBoolean(), any(Message.class));
+
+        mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE2, mDatagram,
+                true, mResultListener::offer);
+
+        processAllMessages();
+
+        mInOrder.verify(mMockDatagramController).isPollingInIdleState();
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
+                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED), eq(0),
+                        eq(SatelliteManager.SATELLITE_SERVICE_ERROR));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
+                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+        verifyNoMoreInteractions(mMockDatagramController);
+
+        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_SERVICE_ERROR);
+    }
+
+    @Test
+    public void testSendSatelliteDatagram_usingCommandsInterface_phoneNull() throws Exception {
+        doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {null});
+
+        mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
+                true, mResultListener::offer);
+
+        processAllMessages();
+
+        mInOrder.verify(mMockDatagramController).isPollingInIdleState();
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
+                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED), eq(0),
+                        eq(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
+                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+        verifyNoMoreInteractions(mMockDatagramController);
+
+        assertThat(mResultListener.peek())
+                .isEqualTo(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+    }
+
+    @Test
+    public void testSendSatelliteDatagram_usingCommandsInterface_success() throws  Exception {
+        doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone});
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[0];
+
+            mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
+                            new AsyncResult(message.obj, null, null))
+                    .sendToTarget();
+            return null;
+        }).when(mPhone).sendSatelliteDatagram(any(Message.class), any(SatelliteDatagram.class),
+                anyBoolean());
+
+        mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE2, mDatagram,
+                true, mResultListener::offer);
+
+        processAllMessages();
+
+        mInOrder.verify(mMockDatagramController).isPollingInIdleState();
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
+                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS), eq(0),
+                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
+                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+        verifyNoMoreInteractions(mMockDatagramController);
+
+        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE);
+    }
+
+    @Test
+    public void testSendSatelliteDatagram_usingCommandsInterface_failure() throws  Exception {
+        doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone});
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[0];
+
+            mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
+                            new AsyncResult(message.obj, null,
+                                    new SatelliteManager.SatelliteException(
+                                            SatelliteManager.SATELLITE_SERVICE_ERROR)))
+                    .sendToTarget();
+            return null;
+        }).when(mPhone).sendSatelliteDatagram(any(Message.class), any(SatelliteDatagram.class),
+                anyBoolean());
+
+        mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
+                true, mResultListener::offer);
+
+        processAllMessages();
+
+        mInOrder.verify(mMockDatagramController).isPollingInIdleState();
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
+                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED), eq(0),
+                        eq(SatelliteManager.SATELLITE_SERVICE_ERROR));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
+                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+        verifyNoMoreInteractions(mMockDatagramController);
+
+        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_SERVICE_ERROR);
+    }
+
+    @Test
+    public void testSendSatelliteDatagram_DemoMode_Align_Success() throws Exception {
+        mTestDemoModeDatagramDispatcher.setDemoMode(true);
+        mTestDemoModeDatagramDispatcher.onDeviceAlignedWithSatellite(true);
+        doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone});
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[0];
+
+            mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
+                            new AsyncResult(message.obj, null, null))
+                    .sendToTarget();
+            return null;
+        }).when(mPhone).sendSatelliteDatagram(any(Message.class), any(SatelliteDatagram.class),
+                anyBoolean());
+
+        mTestDemoModeDatagramDispatcher.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
+                true, mResultListener::offer);
+
+        processAllMessages();
+
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
+                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS), eq(0),
+                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
+                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE);
+        mTestDemoModeDatagramDispatcher.setDemoMode(false);
+        mTestDemoModeDatagramDispatcher.onDeviceAlignedWithSatellite(false);
+    }
+
+    @Test
+    public void testSendSatelliteDatagram_DemoMode_Align_failed() throws Exception {
+        long previousTimer = mTestDemoModeDatagramDispatcher.getSatelliteAlignedTimeoutDuration();
+        mTestDemoModeDatagramDispatcher.setDemoMode(true);
+        mTestDemoModeDatagramDispatcher.setDuration(TEST_EXPIRE_TIMER_SATELLITE_ALIGN);
+        mTestDemoModeDatagramDispatcher.onDeviceAlignedWithSatellite(false);
+
+        doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone});
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[0];
+
+            mTestDemoModeDatagramDispatcher.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
+                            new AsyncResult(message.obj, null, null))
+                    .sendToTarget();
+            return null;
+        }).when(mPhone).sendSatelliteDatagram(any(Message.class), any(SatelliteDatagram.class),
+                anyBoolean());
+
+        mTestDemoModeDatagramDispatcher.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
+                true, mResultListener::offer);
+
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
+                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+        processAllFutureMessages();
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED),
+                        anyInt(), eq(SatelliteManager.SATELLITE_NOT_REACHABLE));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
+                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_NOT_REACHABLE);
+        mTestDemoModeDatagramDispatcher.setDemoMode(false);
+        mTestDemoModeDatagramDispatcher.onDeviceAlignedWithSatellite(false);
+        mTestDemoModeDatagramDispatcher.setDuration(previousTimer);
+    }
+
+    @Test
+    public void testSendSatelliteDatagram_DemoMode_data_type_location_sharing() throws Exception {
+        mTestDemoModeDatagramDispatcher.setDemoMode(true);
+        doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone});
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[0];
+
+            mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
+                            new AsyncResult(message.obj, null, null))
+                    .sendToTarget();
+            return null;
+        }).when(mPhone).sendSatelliteDatagram(any(Message.class), any(SatelliteDatagram.class),
+                anyBoolean());
+
+        mTestDemoModeDatagramDispatcher.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE2, mDatagram,
+                true, mResultListener::offer);
+
+        processAllMessages();
+
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
+                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+
+        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE);
+
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS), eq(0),
+                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
+                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+
+        mTestDemoModeDatagramDispatcher.setDemoMode(false);
+        mTestDemoModeDatagramDispatcher.onDeviceAlignedWithSatellite(false);
+    }
+
+    @Test
+    public void testSatelliteModemBusy_modemPollingDatagram_sendingDelayed() {
+        when(mMockDatagramController.isPollingInIdleState()).thenReturn(false);
+
+        mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
+                true, mResultListener::offer);
+        processAllMessages();
+        // As modem is busy receiving datagrams, sending datagram did not proceed further.
+        mInOrder.verify(mMockDatagramController).isPollingInIdleState();
+        verifyNoMoreInteractions(mMockDatagramController);
+    }
+
+    @Test
+    public void testOnSatelliteModemStateChanged_modemStateListening() {
+        mDatagramDispatcherUT.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_LISTENING);
+        processAllMessages();
+        verifyNoMoreInteractions(mMockDatagramController);
+    }
+
+    @Test
+    public void testOnSatelliteModemStateChanged_modemStateOff_modemSendingDatagrams() {
+        mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
+                true, mResultListener::offer);
+
+        mDatagramDispatcherUT.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+
+        processAllMessages();
+
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(anyInt(),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED),
+                        eq(1), eq(SatelliteManager.SATELLITE_REQUEST_ABORTED));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(anyInt(),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE),
+                        eq(0), eq(SatelliteManager.SATELLITE_ERROR_NONE));
+    }
+
+    @Test
+    public void testOnSatelliteModemStateChanged_modemStateOff_modemNotSendingDatagrams() {
+        mDatagramDispatcherUT.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+
+        processAllMessages();
+
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(anyInt(),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE),
+                        eq(0), eq(SatelliteManager.SATELLITE_ERROR_NONE));
+    }
+
+    private static class TestDatagramDispatcher extends DatagramDispatcher {
+        private long mLong = SATELLITE_ALIGN_TIMEOUT;
+
+        TestDatagramDispatcher(@NonNull Context context, @NonNull Looper looper,
+                @NonNull DatagramController datagramController) {
+            super(context, looper, datagramController);
+        }
+
+        @Override
+        protected void setDemoMode(boolean isDemoMode) {
+            super.setDemoMode(isDemoMode);
+        }
+
+        @Override
+        protected  void onDeviceAlignedWithSatellite(boolean isAligned) {
+            super.onDeviceAlignedWithSatellite(isAligned);
+        }
+
+        @Override
+        protected long getSatelliteAlignedTimeoutDuration() {
+            return mLong;
+        }
+
+        public void setDuration(long duration) {
+            mLong = duration;
+        }
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java
new file mode 100644
index 0000000..1c3777d
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java
@@ -0,0 +1,507 @@
+/*
+ * 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.satellite;
+
+import static com.android.internal.telephony.satellite.DatagramController.SATELLITE_ALIGN_TIMEOUT;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.provider.Telephony;
+import android.telephony.satellite.ISatelliteDatagramCallback;
+import android.test.mock.MockContentResolver;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.os.AsyncResult;
+import android.os.Looper;
+import android.os.Message;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.telephony.satellite.SatelliteDatagram;
+import android.telephony.satellite.SatelliteManager;
+import android.util.Pair;
+
+import com.android.internal.telephony.IVoidConsumer;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class DatagramReceiverTest extends TelephonyTest {
+    private static final String TAG = "DatagramReceiverTest";
+    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 DatagramReceiver mDatagramReceiverUT;
+    private DatagramReceiver.SatelliteDatagramListenerHandler mSatelliteDatagramListenerHandler;
+    private TestDatagramReceiver mTestDemoModeDatagramReceiver;
+
+    @Mock private SatelliteController mMockSatelliteController;
+    @Mock private DatagramController mMockDatagramController;
+    @Mock private SatelliteModemInterface mMockSatelliteModemInterface;
+    @Mock private ControllerMetricsStats mMockControllerMetricsStats;
+
+    /** Variables required to receive datagrams in the unit tests. */
+    LinkedBlockingQueue<Integer> mResultListener;
+    SatelliteDatagram mDatagram;
+    InOrder mInOrder;
+    private FakeSatelliteProvider mFakeSatelliteProvider;
+    private MockContentResolver mMockContentResolver;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        MockitoAnnotations.initMocks(this);
+        logd(TAG + " Setup!");
+
+        // Setup mock satellite provider DB.
+        mFakeSatelliteProvider = new FakeSatelliteProvider();
+        mMockContentResolver = new MockContentResolver();
+        mMockContentResolver.addProvider(
+                Telephony.SatelliteDatagrams.PROVIDER_NAME, mFakeSatelliteProvider);
+        doReturn(mMockContentResolver).when(mContext).getContentResolver();
+
+        replaceInstance(SatelliteController.class, "sInstance", null, mMockSatelliteController);
+        replaceInstance(DatagramController.class, "sInstance", null,
+                mMockDatagramController);
+        replaceInstance(SatelliteModemInterface.class, "sInstance", null,
+                mMockSatelliteModemInterface);
+        replaceInstance(ControllerMetricsStats.class, "sInstance", null,
+                mMockControllerMetricsStats);
+
+        mDatagramReceiverUT = DatagramReceiver.make(mContext, Looper.myLooper(),
+                mMockDatagramController);
+        mTestDemoModeDatagramReceiver = new TestDatagramReceiver(mContext, Looper.myLooper(),
+                mMockDatagramController);
+        mSatelliteDatagramListenerHandler = new DatagramReceiver.SatelliteDatagramListenerHandler(
+                Looper.myLooper(), SUB_ID);
+
+        mResultListener = new LinkedBlockingQueue<>(1);
+        mDatagram = new SatelliteDatagram(TEST_MESSAGE.getBytes());
+        mInOrder = inOrder(mMockDatagramController);
+
+        when(mMockDatagramController.isSendingInIdleState()).thenReturn(true);
+        when(mMockDatagramController.isPollingInIdleState()).thenReturn(true);
+        processAllMessages();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        logd(TAG + " tearDown");
+        mFakeSatelliteProvider.shutdown();
+        mDatagramReceiverUT.destroy();
+        mDatagramReceiverUT = null;
+        mTestDemoModeDatagramReceiver = null;
+        mResultListener = null;
+        mDatagram = null;
+        mInOrder = null;
+        super.tearDown();
+    }
+
+    @Test
+    public void testPollPendingSatelliteDatagrams_usingSatelliteModemInterface_success()
+            throws Exception {
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[0];
+
+            mDatagramReceiverUT.obtainMessage(2 /*EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE*/,
+                            new AsyncResult(message.obj, null, null))
+                    .sendToTarget();
+            return null;
+        }).when(mMockSatelliteModemInterface).pollPendingSatelliteDatagrams(any(Message.class));
+
+        mDatagramReceiverUT.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer);
+
+        processAllMessages();
+
+        mInOrder.verify(mMockDatagramController)
+                .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING), eq(0),
+                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+
+        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE);
+    }
+
+    @Test
+    public void testPollPendingSatelliteDatagrams_usingSatelliteModemInterface_failure()
+            throws Exception {
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[0];
+
+            mDatagramReceiverUT.obtainMessage(2 /*EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE*/,
+                            new AsyncResult(message.obj, null,
+                                    new SatelliteManager.SatelliteException(
+                                            SatelliteManager.SATELLITE_SERVICE_ERROR)))
+                    .sendToTarget();
+            return null;
+        }).when(mMockSatelliteModemInterface).pollPendingSatelliteDatagrams(any(Message.class));
+
+        mDatagramReceiverUT.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer);
+
+        processAllMessages();
+
+        mInOrder.verify(mMockDatagramController)
+                .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING), eq(0),
+                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+        mInOrder.verify(mMockDatagramController)
+                .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED),
+                        eq(0), eq(SatelliteManager.SATELLITE_SERVICE_ERROR));
+
+        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_SERVICE_ERROR);
+    }
+
+    @Test
+    public void testPollPendingSatelliteDatagrams_usingCommandsInterface_phoneNull()
+            throws Exception {
+        doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {null});
+
+        mDatagramReceiverUT.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer);
+
+        processAllMessages();
+
+        mInOrder.verify(mMockDatagramController)
+                .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING), eq(0),
+                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+        mInOrder.verify(mMockDatagramController)
+                .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED),
+                        eq(0), eq(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE));
+        mInOrder.verify(mMockDatagramController)
+                .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE),
+                        eq(0), eq(SatelliteManager.SATELLITE_ERROR_NONE));
+
+        assertThat(mResultListener.peek())
+                .isEqualTo(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+    }
+
+    @Test
+    public void testPollPendingSatelliteDatagrams_usingCommandsInterface_success()
+            throws Exception {
+        doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone});
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[0];
+
+            mDatagramReceiverUT.obtainMessage(2 /*EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE*/,
+                            new AsyncResult(message.obj, null, null))
+                    .sendToTarget();
+            return null;
+        }).when(mPhone).pollPendingSatelliteDatagrams(any(Message.class));
+
+        mDatagramReceiverUT.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer);
+
+        processAllMessages();
+
+        mInOrder.verify(mMockDatagramController)
+                .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING), eq(0),
+                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+
+        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_ERROR_NONE);
+    }
+
+    @Test
+    public void testPollPendingSatelliteDatagrams_usingCommandsInterface_failure()
+            throws Exception {
+        doReturn(false).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone});
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[0];
+
+            mDatagramReceiverUT.obtainMessage(2 /*EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE*/,
+                            new AsyncResult(message.obj, null,
+                                    new SatelliteManager.SatelliteException(
+                                            SatelliteManager.SATELLITE_SERVICE_ERROR)))
+                    .sendToTarget();
+            return null;
+        }).when(mPhone).pollPendingSatelliteDatagrams(any(Message.class));
+
+        mDatagramReceiverUT.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer);
+
+        processAllMessages();
+
+        mInOrder.verify(mMockDatagramController)
+                .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING), eq(0),
+                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+        mInOrder.verify(mMockDatagramController)
+                .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED),
+                        eq(0), eq(SatelliteManager.SATELLITE_SERVICE_ERROR));
+
+        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_SERVICE_ERROR);
+    }
+
+    @Test
+    public void testSatelliteDatagramReceived_receiveNone() {
+        mSatelliteDatagramListenerHandler.obtainMessage(1 /*EVENT_SATELLITE_DATAGRAM_RECEIVED*/,
+                new AsyncResult(null, new Pair<>(null, 0), null))
+                .sendToTarget();
+
+        processAllMessages();
+
+        mInOrder.verify(mMockDatagramController)
+                .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE),
+                        eq(0), eq(SatelliteManager.SATELLITE_ERROR_NONE));
+        mInOrder.verify(mMockDatagramController)
+                .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE),
+                        eq(0), eq(SatelliteManager.SATELLITE_ERROR_NONE));
+    }
+
+    @Test
+    public void testSatelliteDatagramReceived_success_zeroPendingCount() {
+        mSatelliteDatagramListenerHandler.obtainMessage(1 /*EVENT_SATELLITE_DATAGRAM_RECEIVED*/,
+                        new AsyncResult(null, new Pair<>(mDatagram, 0), null))
+                .sendToTarget();
+
+        processAllMessages();
+
+        mInOrder.verify(mMockDatagramController)
+                .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS),
+                        eq(0), eq(SatelliteManager.SATELLITE_ERROR_NONE));
+        mInOrder.verify(mMockDatagramController)
+                .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE),
+                        eq(0), eq(SatelliteManager.SATELLITE_ERROR_NONE));
+    }
+
+    @Test
+    public void testSatelliteDatagramReceived_success_nonZeroPendingCount() {
+        mSatelliteDatagramListenerHandler.obtainMessage(1 /*EVENT_SATELLITE_DATAGRAM_RECEIVED*/,
+                        new AsyncResult(null, new Pair<>(mDatagram, 10), null))
+                .sendToTarget();
+
+        processAllMessages();
+
+        mInOrder.verify(mMockDatagramController)
+                .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS),
+                        eq(10), eq(SatelliteManager.SATELLITE_ERROR_NONE));
+    }
+
+    @Test
+    public void testPollPendingSatelliteDatagrams_DemoMode_Align_succeed() throws Exception {
+        // Checks invalid case only as SatelliteController does not exist in unit test
+        mTestDemoModeDatagramReceiver.setDemoMode(true);
+        mTestDemoModeDatagramReceiver.onDeviceAlignedWithSatellite(true);
+        when(mMockDatagramController.getDemoModeDatagram()).thenReturn(mDatagram);
+        mTestDemoModeDatagramReceiver.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer);
+        processAllMessages();
+        verify(mMockDatagramController, times(1)).getDemoModeDatagram();
+        verify(mMockDatagramController)
+                .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING),
+                        anyInt(),
+                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+        verify(mMockDatagramController)
+                .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED),
+                        anyInt(),
+                        eq(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE));
+        verify(mMockDatagramController)
+                .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE),
+                        anyInt(),
+                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+        assertThat(mResultListener.peek())
+                .isEqualTo(SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE);
+    }
+
+    @Test
+    public void testPollPendingSatelliteDatagrams_DemoMode_Align_failed() throws Exception {
+        // Checks invalid case only as SatelliteController does not exist in unit test
+        long previousTimer = mTestDemoModeDatagramReceiver.getSatelliteAlignedTimeoutDuration();
+        mTestDemoModeDatagramReceiver.setDemoMode(true);
+        mTestDemoModeDatagramReceiver.setDuration(TEST_EXPIRE_TIMER_SATELLITE_ALIGN);
+        mTestDemoModeDatagramReceiver.onDeviceAlignedWithSatellite(false);
+        when(mMockDatagramController.getDemoModeDatagram()).thenReturn(mDatagram);
+        mTestDemoModeDatagramReceiver.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer);
+        processAllMessages();
+        verify(mMockDatagramController, never()).getDemoModeDatagram();
+        verify(mMockDatagramController)
+                .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING),
+                        anyInt(),
+                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+        processAllFutureMessages();
+        verify(mMockDatagramController)
+                .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED),
+                        anyInt(),
+                        eq(SatelliteManager.SATELLITE_NOT_REACHABLE));
+        verify(mMockDatagramController)
+                .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE),
+                        anyInt(),
+                        eq(SatelliteManager.SATELLITE_ERROR_NONE));
+        assertThat(mResultListener.peek())
+                .isEqualTo(SatelliteManager.SATELLITE_NOT_REACHABLE);
+
+        mTestDemoModeDatagramReceiver.setDemoMode(false);
+        mTestDemoModeDatagramReceiver.onDeviceAlignedWithSatellite(false);
+        mTestDemoModeDatagramReceiver.setDuration(previousTimer);
+    }
+
+    @Test
+    public void testSatelliteModemBusy_modemSendingDatagram_pollingFailure() {
+        when(mMockDatagramController.isSendingInIdleState()).thenReturn(false);
+
+        mDatagramReceiverUT.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer);
+        processAllMessages();
+        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_MODEM_BUSY);
+    }
+
+    @Test
+    public void testSatelliteModemBusy_modemPollingDatagrams_pollingFailure() {
+        when(mMockDatagramController.isSendingInIdleState()).thenReturn(false);
+        when(mMockDatagramController.isPollingInIdleState()).thenReturn(true);
+
+        mDatagramReceiverUT.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer);
+        processAllMessages();
+        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_MODEM_BUSY);
+    }
+
+    @Test
+    public void testOnSatelliteModemStateChanged_modemStateIdle() {
+        mDatagramReceiverUT.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+        processAllMessages();
+        verifyNoMoreInteractions(mMockDatagramController);
+    }
+
+    @Test
+    public void testOnSatelliteModemStateChanged_modemStateOff_modemReceivingDatagrams() {
+        when(mMockDatagramController.isReceivingDatagrams()).thenReturn(true);
+        when(mMockDatagramController.getReceivePendingCount()).thenReturn(10);
+
+        mDatagramReceiverUT.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+
+        processAllMessages();
+
+        mInOrder.verify(mMockDatagramController)
+                .updateReceiveStatus(anyInt(),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED),
+                        eq(10), eq(SatelliteManager.SATELLITE_REQUEST_ABORTED));
+        mInOrder.verify(mMockDatagramController)
+                .updateReceiveStatus(anyInt(),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE),
+                        eq(0), eq(SatelliteManager.SATELLITE_ERROR_NONE));
+    }
+
+    @Test
+    public void testOnSatelliteModemStateChanged_modemStateOff_modemNotReceivingDatagrams() {
+        when(mMockDatagramController.isReceivingDatagrams()).thenReturn(false);
+
+        mDatagramReceiverUT.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+
+        processAllMessages();
+
+        mInOrder.verify(mMockDatagramController)
+                .updateReceiveStatus(anyInt(),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE),
+                        eq(0), eq(SatelliteManager.SATELLITE_ERROR_NONE));
+    }
+
+    @Test
+    public void testRegisterForSatelliteDatagram_satelliteNotSupported() {
+        when(mMockSatelliteController.isSatelliteSupported()).thenReturn(false);
+
+        ISatelliteDatagramCallback callback = new ISatelliteDatagramCallback() {
+            @Override
+            public void onSatelliteDatagramReceived(long datagramId, SatelliteDatagram datagram,
+                    int pendingCount, IVoidConsumer callback) throws RemoteException {
+                logd("onSatelliteDatagramReceived");
+            }
+
+            @Override
+            public IBinder asBinder() {
+                return null;
+            }
+        };
+
+        assertThat(mDatagramReceiverUT.registerForSatelliteDatagram(SUB_ID, callback))
+                .isEqualTo(SatelliteManager.SATELLITE_NOT_SUPPORTED);
+    }
+
+    private static class TestDatagramReceiver extends DatagramReceiver {
+        private long mLong =  SATELLITE_ALIGN_TIMEOUT;
+
+        TestDatagramReceiver(@NonNull Context context, @NonNull Looper looper,
+                @NonNull DatagramController datagramController) {
+            super(context, looper, datagramController);
+        }
+
+        @Override
+        protected void setDemoMode(boolean isDemoMode) {
+            super.setDemoMode(isDemoMode);
+        }
+
+        @Override
+        protected void onDeviceAlignedWithSatellite(boolean isAligned) {
+            super.onDeviceAlignedWithSatellite(isAligned);
+        }
+
+        @Override
+        protected long getSatelliteAlignedTimeoutDuration() {
+            return mLong;
+        }
+
+        public void setDuration(long duration) {
+            mLong = duration;
+        }
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/FakeSatelliteProvider.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/FakeSatelliteProvider.java
new file mode 100644
index 0000000..b0c6a81
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/FakeSatelliteProvider.java
@@ -0,0 +1,103 @@
+/*
+ * 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.satellite;
+
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Telephony;
+import android.test.mock.MockContentProvider;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+public class FakeSatelliteProvider extends MockContentProvider {
+    private static final String TAG = "FakeSatelliteProvider";
+
+    private InMemorySatelliteProviderDbHelper mDbHelper = new InMemorySatelliteProviderDbHelper();
+
+    private class InMemorySatelliteProviderDbHelper extends SQLiteOpenHelper {
+
+        InMemorySatelliteProviderDbHelper() {
+            super(InstrumentationRegistry.getTargetContext(),
+                    null,    // db file name is null for in-memory db
+                    null,    // CursorFactory is null by default
+                    1);      // db version is no-op for tests
+            Log.d(TAG, "InMemorySatelliteProviderDbHelper creating in-memory database");
+        }
+        public static String getStringForDatagramTableCreation(String tableName) {
+            return "CREATE TABLE " + tableName + "("
+                    + Telephony.SatelliteDatagrams.COLUMN_UNIQUE_KEY_DATAGRAM_ID
+                    + " INTEGER PRIMARY KEY,"
+                    + Telephony.SatelliteDatagrams.COLUMN_DATAGRAM + " BLOB DEFAULT ''" +
+                    ");";
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            Log.d(TAG, "InMemoryTelephonyProviderDbHelper onCreate:"
+                    + " creating satellite incoming datagram table");
+            db.execSQL(getStringForDatagramTableCreation(Telephony.SatelliteDatagrams.TABLE_NAME));
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            // Do nothing.
+        }
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        Log.d(TAG, "insert. values=" + values);
+        SQLiteDatabase db = mDbHelper.getWritableDatabase();
+        long id = db.insert(Telephony.SatelliteDatagrams.TABLE_NAME, null, values);
+        return ContentUris.withAppendedId(Telephony.SatelliteDatagrams.CONTENT_URI, id);
+    }
+
+    @Override
+    public synchronized int delete(Uri url, String where, String[] whereArgs) {
+        return mDbHelper.getWritableDatabase()
+                .delete(Telephony.SatelliteDatagrams.TABLE_NAME, where, whereArgs);
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        return mDbHelper.getReadableDatabase().query(Telephony.SatelliteDatagrams.TABLE_NAME,
+                projection, selection, selectionArgs, null, null, sortOrder);
+    }
+
+    @Override
+    public Bundle call(String method, String request, Bundle args) {
+        return null;
+    }
+
+    @Override
+    public final int update(Uri uri, ContentValues values, String where, String[] selectionArgs) {
+        // Do nothing.
+        return 0;
+    }
+
+    @Override
+    public void shutdown() {
+        mDbHelper.close();
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteCapabilitiesTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteCapabilitiesTest.java
new file mode 100644
index 0000000..29473c9
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteCapabilitiesTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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.satellite;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.os.Parcel;
+import android.telephony.satellite.AntennaDirection;
+import android.telephony.satellite.AntennaPosition;
+import android.telephony.satellite.SatelliteCapabilities;
+import android.telephony.satellite.SatelliteManager;
+
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class SatelliteCapabilitiesTest {
+
+    private AntennaDirection mAntennaDirection = new AntennaDirection(1,1,1);
+
+    @Test
+    public void testParcel() {
+        Set<Integer> satelliteRadioTechnologies = new HashSet<>();
+        satelliteRadioTechnologies.add(SatelliteManager.NT_RADIO_TECHNOLOGY_NB_IOT_NTN);
+        satelliteRadioTechnologies.add(SatelliteManager.NT_RADIO_TECHNOLOGY_NR_NTN);
+
+        Map<Integer, AntennaPosition> antennaPositionMap = new HashMap<>();
+        AntennaPosition antennaPosition = new AntennaPosition(mAntennaDirection,
+                SatelliteManager.DEVICE_HOLD_POSITION_PORTRAIT);
+        antennaPositionMap.put(SatelliteManager.DISPLAY_MODE_OPENED, antennaPosition);
+
+        SatelliteCapabilities capabilities = new SatelliteCapabilities(satelliteRadioTechnologies,
+                true, 10, antennaPositionMap);
+
+        Parcel p = Parcel.obtain();
+        capabilities.writeToParcel(p, 0);
+        p.setDataPosition(0);
+
+        SatelliteCapabilities fromParcel = SatelliteCapabilities.CREATOR.createFromParcel(p);
+        assertThat(capabilities).isEqualTo(fromParcel);
+    }
+
+    @Test
+    public void testParcel_emptySatelliteRadioTechnologies() {
+        Set<Integer> satelliteRadioTechnologies = new HashSet<>();
+
+        Map<Integer, AntennaPosition> antennaPositionMap = new HashMap<>();
+        AntennaPosition antennaPosition1 = new AntennaPosition(mAntennaDirection,
+                SatelliteManager.DEVICE_HOLD_POSITION_PORTRAIT);
+        AntennaPosition antennaPosition2 = new AntennaPosition(mAntennaDirection,
+                SatelliteManager.DEVICE_HOLD_POSITION_LANDSCAPE_LEFT);
+        antennaPositionMap.put(SatelliteManager.DISPLAY_MODE_OPENED, antennaPosition1);
+        antennaPositionMap.put(SatelliteManager.DISPLAY_MODE_CLOSED, antennaPosition2);
+
+        SatelliteCapabilities capabilities = new SatelliteCapabilities(satelliteRadioTechnologies,
+                false, 100, antennaPositionMap);
+
+        Parcel p = Parcel.obtain();
+        capabilities.writeToParcel(p, 0);
+        p.setDataPosition(0);
+
+        SatelliteCapabilities fromParcel = SatelliteCapabilities.CREATOR.createFromParcel(p);
+        assertThat(capabilities).isEqualTo(fromParcel);
+    }
+
+
+    @Test
+    public void testParcel_emptyAntennaPosition() {
+        Set<Integer> satelliteRadioTechnologies = new HashSet<>();
+        satelliteRadioTechnologies.add(SatelliteManager.NT_RADIO_TECHNOLOGY_EMTC_NTN);
+
+        SatelliteCapabilities capabilities = new SatelliteCapabilities(satelliteRadioTechnologies,
+                true, 0, new HashMap<>());
+
+        Parcel p = Parcel.obtain();
+        capabilities.writeToParcel(p, 0);
+        p.setDataPosition(0);
+
+        SatelliteCapabilities fromParcel = SatelliteCapabilities.CREATOR.createFromParcel(p);
+        assertThat(capabilities).isEqualTo(fromParcel);
+    }
+
+    @Test
+    public void testEquals() {
+        Set<Integer> satelliteRadioTechnologies = new HashSet<>();
+        satelliteRadioTechnologies.add(SatelliteManager.NT_RADIO_TECHNOLOGY_NB_IOT_NTN);
+        satelliteRadioTechnologies.add(SatelliteManager.NT_RADIO_TECHNOLOGY_NR_NTN);
+
+        AntennaPosition antennaPosition1 = new AntennaPosition(mAntennaDirection,
+                SatelliteManager.DEVICE_HOLD_POSITION_PORTRAIT);
+        AntennaPosition antennaPosition2 = new AntennaPosition(mAntennaDirection,
+                SatelliteManager.DEVICE_HOLD_POSITION_LANDSCAPE_LEFT);
+
+        Map<Integer, AntennaPosition> antennaPositionMap1 = new HashMap<>();
+        antennaPositionMap1.put(SatelliteManager.DISPLAY_MODE_OPENED, antennaPosition1);
+        antennaPositionMap1.put(SatelliteManager.DISPLAY_MODE_CLOSED, antennaPosition2);
+
+        SatelliteCapabilities satelliteCapabilities1 =
+                new SatelliteCapabilities(satelliteRadioTechnologies, true, 10,
+                        antennaPositionMap1);
+        SatelliteCapabilities satelliteCapabilities2 =
+                new SatelliteCapabilities(satelliteRadioTechnologies, true, 10,
+                        antennaPositionMap1);
+        assertEquals(satelliteCapabilities1, satelliteCapabilities2);
+
+        Map<Integer, AntennaPosition> antennaPositionMap2 = new HashMap<>();
+        antennaPositionMap2.put(SatelliteManager.DISPLAY_MODE_CLOSED, antennaPosition1);
+        antennaPositionMap2.put(SatelliteManager.DISPLAY_MODE_OPENED, antennaPosition2);
+        satelliteCapabilities2 =
+                new SatelliteCapabilities(satelliteRadioTechnologies, true, 10,
+                        antennaPositionMap2);
+        assertNotEquals(satelliteCapabilities1, satelliteCapabilities2);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
new file mode 100644
index 0000000..f6ed2e2
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
@@ -0,0 +1,2025 @@
+/*
+ * 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.satellite;
+
+import static android.telephony.satellite.SatelliteManager.KEY_DEMO_MODE_ENABLED;
+import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_CAPABILITIES;
+import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_COMMUNICATION_ALLOWED;
+import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_ENABLED;
+import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_NEXT_VISIBILITY;
+import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_PROVISIONED;
+import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_SUPPORTED;
+import static android.telephony.satellite.SatelliteManager.NT_RADIO_TECHNOLOGY_EMTC_NTN;
+import static android.telephony.satellite.SatelliteManager.NT_RADIO_TECHNOLOGY_NR_NTN;
+import static android.telephony.satellite.SatelliteManager.NT_RADIO_TECHNOLOGY_PROPRIETARY;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_ERROR;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_ERROR_NONE;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_INVALID_ARGUMENTS;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_INVALID_MODEM_STATE;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_INVALID_TELEPHONY_STATE;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_OFF;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_NOT_AUTHORIZED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_NOT_SUPPORTED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_NO_RESOURCES;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RADIO_NOT_AVAILABLE;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_REQUEST_IN_PROGRESS;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_SERVICE_NOT_PROVISIONED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_SERVICE_PROVISION_IN_PROGRESS;
+
+import static com.android.internal.telephony.satellite.SatelliteController.SATELLITE_MODE_ENABLED_FALSE;
+import static com.android.internal.telephony.satellite.SatelliteController.SATELLITE_MODE_ENABLED_TRUE;
+
+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.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.ICancellationSignal;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ResultReceiver;
+import android.telephony.Rlog;
+import android.telephony.satellite.ISatelliteDatagramCallback;
+import android.telephony.satellite.ISatelliteProvisionStateCallback;
+import android.telephony.satellite.ISatelliteStateCallback;
+import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
+import android.telephony.satellite.SatelliteCapabilities;
+import android.telephony.satellite.SatelliteDatagram;
+import android.telephony.satellite.SatelliteManager;
+import android.telephony.satellite.SatelliteManager.SatelliteException;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.telephony.IIntegerConsumer;
+import com.android.internal.telephony.IVoidConsumer;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats;
+import com.android.internal.telephony.satellite.metrics.ProvisionMetricsStats;
+import com.android.internal.telephony.satellite.metrics.SessionMetricsStats;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class SatelliteControllerTest extends TelephonyTest {
+    private static final String TAG = "SatelliteControllerTest";
+    private static final long TIMEOUT = 500;
+    private static final int SUB_ID = 0;
+    private static final int MAX_BYTES_PER_OUT_GOING_DATAGRAM = 339;
+    private static final String TEST_SATELLITE_TOKEN = "TEST_SATELLITE_TOKEN";
+    private static final String TEST_NEXT_SATELLITE_TOKEN = "TEST_NEXT_SATELLITE_TOKEN";
+
+    private TestSatelliteController mSatelliteControllerUT;
+    private TestSharedPreferences mSharedPreferences;
+
+    @Mock private DatagramController mMockDatagramController;
+    @Mock private SatelliteModemInterface mMockSatelliteModemInterface;
+    @Mock private SatelliteSessionController mMockSatelliteSessionController;
+    @Mock private PointingAppController mMockPointingAppController;
+    @Mock private ControllerMetricsStats mMockControllerMetricsStats;
+    @Mock private ProvisionMetricsStats mMockProvisionMetricsStats;
+    @Mock private SessionMetricsStats mMockSessionMetricsStats;
+    private List<Integer> mIIntegerConsumerResults =  new ArrayList<>();
+    @Mock private ISatelliteTransmissionUpdateCallback mStartTransmissionUpdateCallback;
+    @Mock private ISatelliteTransmissionUpdateCallback mStopTransmissionUpdateCallback;
+    private Semaphore mIIntegerConsumerSemaphore = new Semaphore(0);
+    private IIntegerConsumer mIIntegerConsumer = new IIntegerConsumer.Stub() {
+        @Override
+        public void accept(int result) {
+            logd("mIIntegerConsumer: result=" + result);
+            mIIntegerConsumerResults.add(result);
+            try {
+                mIIntegerConsumerSemaphore.release();
+            } catch (Exception ex) {
+                loge("mIIntegerConsumer: Got exception in releasing semaphore, ex=" + ex);
+            }
+        }
+    };
+
+    private boolean mIsSatelliteServiceSupported = true;
+    private boolean mIsPointingRequired = true;
+    private Set<Integer> mSupportedRadioTechnologies = new HashSet<>(Arrays.asList(
+            NT_RADIO_TECHNOLOGY_NR_NTN,
+            NT_RADIO_TECHNOLOGY_EMTC_NTN,
+            NT_RADIO_TECHNOLOGY_PROPRIETARY));
+    private SatelliteCapabilities mSatelliteCapabilities = new SatelliteCapabilities(
+            mSupportedRadioTechnologies, mIsPointingRequired, MAX_BYTES_PER_OUT_GOING_DATAGRAM,
+            new HashMap<>());
+    private Semaphore mSatelliteCapabilitiesSemaphore = new Semaphore(0);
+    private SatelliteCapabilities mQueriedSatelliteCapabilities = null;
+    private int mQueriedSatelliteCapabilitiesResultCode = SATELLITE_ERROR_NONE;
+    private ResultReceiver mSatelliteCapabilitiesReceiver = new ResultReceiver(null) {
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            mQueriedSatelliteCapabilitiesResultCode = resultCode;
+            if (resultCode == SATELLITE_ERROR_NONE) {
+                if (resultData.containsKey(KEY_SATELLITE_CAPABILITIES)) {
+                    mQueriedSatelliteCapabilities = resultData.getParcelable(
+                            KEY_SATELLITE_CAPABILITIES, SatelliteCapabilities.class);
+                } else {
+                    loge("KEY_SATELLITE_SUPPORTED does not exist.");
+                    mQueriedSatelliteCapabilities = null;
+                }
+            } else {
+                logd("mSatelliteSupportReceiver: resultCode=" + resultCode);
+                mQueriedSatelliteCapabilities = null;
+            }
+            try {
+                mSatelliteCapabilitiesSemaphore.release();
+            } catch (Exception ex) {
+                loge("mSatelliteSupportReceiver: Got exception in releasing semaphore, ex=" + ex);
+            }
+        }
+    };
+
+    private boolean mQueriedSatelliteSupported = false;
+    private int mQueriedSatelliteSupportedResultCode = SATELLITE_ERROR_NONE;
+    private Semaphore mSatelliteSupportSemaphore = new Semaphore(0);
+    private ResultReceiver mSatelliteSupportReceiver = new ResultReceiver(null) {
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            mQueriedSatelliteSupportedResultCode = resultCode;
+            if (resultCode == SATELLITE_ERROR_NONE) {
+                if (resultData.containsKey(KEY_SATELLITE_SUPPORTED)) {
+                    mQueriedSatelliteSupported = resultData.getBoolean(KEY_SATELLITE_SUPPORTED);
+                } else {
+                    loge("KEY_SATELLITE_SUPPORTED does not exist.");
+                    mQueriedSatelliteSupported = false;
+                }
+            } else {
+                logd("mSatelliteSupportReceiver: resultCode=" + resultCode);
+                mQueriedSatelliteSupported = false;
+            }
+            try {
+                mSatelliteSupportSemaphore.release();
+            } catch (Exception ex) {
+                loge("mSatelliteSupportReceiver: Got exception in releasing semaphore, ex=" + ex);
+            }
+        }
+    };
+
+    private boolean mQueriedIsSatelliteEnabled = false;
+    private int mQueriedIsSatelliteEnabledResultCode = SATELLITE_ERROR_NONE;
+    private Semaphore mIsSatelliteEnabledSemaphore = new Semaphore(0);
+    private ResultReceiver mIsSatelliteEnabledReceiver = new ResultReceiver(null) {
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            mQueriedIsSatelliteEnabledResultCode = resultCode;
+            if (resultCode == SATELLITE_ERROR_NONE) {
+                if (resultData.containsKey(KEY_SATELLITE_ENABLED)) {
+                    mQueriedIsSatelliteEnabled = resultData.getBoolean(KEY_SATELLITE_ENABLED);
+                } else {
+                    loge("KEY_SATELLITE_ENABLED does not exist.");
+                    mQueriedIsSatelliteEnabled = false;
+                }
+            } else {
+                logd("mIsSatelliteEnableReceiver: resultCode=" + resultCode);
+                mQueriedIsSatelliteEnabled = false;
+            }
+            try {
+                mIsSatelliteEnabledSemaphore.release();
+            } catch (Exception ex) {
+                loge("mIsSatelliteEnableReceiver: Got exception in releasing semaphore, ex=" + ex);
+            }
+        }
+    };
+
+    private boolean mQueriedIsDemoModeEnabled = false;
+    private int mQueriedIsDemoModeEnabledResultCode = SATELLITE_ERROR_NONE;
+    private Semaphore mIsDemoModeEnabledSemaphore = new Semaphore(0);
+    private ResultReceiver mIsDemoModeEnabledReceiver = new ResultReceiver(null) {
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            mQueriedIsDemoModeEnabledResultCode = resultCode;
+            if (resultCode == SATELLITE_ERROR_NONE) {
+                if (resultData.containsKey(KEY_DEMO_MODE_ENABLED)) {
+                    mQueriedIsDemoModeEnabled = resultData.getBoolean(KEY_DEMO_MODE_ENABLED);
+                } else {
+                    loge("KEY_DEMO_MODE_ENABLED does not exist.");
+                    mQueriedIsDemoModeEnabled = false;
+                }
+            } else {
+                logd("mIsSatelliteEnableReceiver: resultCode=" + resultCode);
+                mQueriedIsDemoModeEnabled = false;
+            }
+            try {
+                mIsDemoModeEnabledSemaphore.release();
+            } catch (Exception ex) {
+                loge("mIsDemoModeEnabledReceiver: Got exception in releasing semaphore, ex=" + ex);
+            }
+        }
+    };
+
+    private boolean mQueriedIsSatelliteProvisioned = false;
+    private int mQueriedIsSatelliteProvisionedResultCode = SATELLITE_ERROR_NONE;
+    private Semaphore mIsSatelliteProvisionedSemaphore = new Semaphore(0);
+    private ResultReceiver mIsSatelliteProvisionedReceiver = new ResultReceiver(null) {
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            mQueriedIsSatelliteProvisionedResultCode = resultCode;
+            if (resultCode == SATELLITE_ERROR_NONE) {
+                if (resultData.containsKey(KEY_SATELLITE_PROVISIONED)) {
+                    mQueriedIsSatelliteProvisioned =
+                            resultData.getBoolean(KEY_SATELLITE_PROVISIONED);
+                } else {
+                    loge("KEY_SATELLITE_PROVISIONED does not exist.");
+                    mQueriedIsSatelliteProvisioned = false;
+                }
+            } else {
+                mQueriedIsSatelliteProvisioned = false;
+            }
+            try {
+                mIsSatelliteProvisionedSemaphore.release();
+            } catch (Exception ex) {
+                loge("mIsSatelliteProvisionedReceiver: Got exception in releasing semaphore ex="
+                        + ex);
+            }
+        }
+    };
+
+    private boolean mQueriedSatelliteAllowed = false;
+    private int mQueriedSatelliteAllowedResultCode = SATELLITE_ERROR_NONE;
+    private Semaphore mSatelliteAllowedSemaphore = new Semaphore(0);
+    private ResultReceiver mSatelliteAllowedReceiver = new ResultReceiver(null) {
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            mQueriedSatelliteAllowedResultCode = resultCode;
+            if (resultCode == SATELLITE_ERROR_NONE) {
+                if (resultData.containsKey(KEY_SATELLITE_COMMUNICATION_ALLOWED)) {
+                    mQueriedSatelliteAllowed = resultData.getBoolean(
+                            KEY_SATELLITE_COMMUNICATION_ALLOWED);
+                } else {
+                    loge("KEY_SATELLITE_COMMUNICATION_ALLOWED does not exist.");
+                    mQueriedSatelliteAllowed = false;
+                }
+            } else {
+                logd("mSatelliteSupportReceiver: resultCode=" + resultCode);
+                mQueriedSatelliteAllowed = false;
+            }
+            try {
+                mSatelliteAllowedSemaphore.release();
+            } catch (Exception ex) {
+                loge("mSatelliteAllowedReceiver: Got exception in releasing semaphore, ex=" + ex);
+            }
+        }
+    };
+
+    private int mQueriedSatelliteVisibilityTime = -1;
+    private int mSatelliteNextVisibilityTime = 3600;
+    private int mQueriedSatelliteVisibilityTimeResultCode = SATELLITE_ERROR_NONE;
+    private Semaphore mSatelliteVisibilityTimeSemaphore = new Semaphore(0);
+    private ResultReceiver mSatelliteVisibilityTimeReceiver = new ResultReceiver(null) {
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            mQueriedSatelliteVisibilityTimeResultCode = resultCode;
+            if (resultCode == SATELLITE_ERROR_NONE) {
+                if (resultData.containsKey(KEY_SATELLITE_NEXT_VISIBILITY)) {
+                    mQueriedSatelliteVisibilityTime = resultData.getInt(
+                            KEY_SATELLITE_NEXT_VISIBILITY);
+                } else {
+                    loge("KEY_SATELLITE_NEXT_VISIBILITY does not exist.");
+                    mQueriedSatelliteVisibilityTime = -1;
+                }
+            } else {
+                logd("mSatelliteSupportReceiver: resultCode=" + resultCode);
+                mQueriedSatelliteVisibilityTime = -1;
+            }
+            try {
+                mSatelliteVisibilityTimeSemaphore.release();
+            } catch (Exception ex) {
+                loge("mSatelliteAllowedReceiver: Got exception in releasing semaphore, ex=" + ex);
+            }
+        }
+    };
+
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        MockitoAnnotations.initMocks(this);
+        logd(TAG + " Setup!");
+
+        replaceInstance(DatagramController.class, "sInstance", null,
+                mMockDatagramController);
+        replaceInstance(SatelliteModemInterface.class, "sInstance", null,
+                mMockSatelliteModemInterface);
+        replaceInstance(SatelliteSessionController.class, "sInstance", null,
+                mMockSatelliteSessionController);
+        replaceInstance(PointingAppController.class, "sInstance", null,
+                mMockPointingAppController);
+        replaceInstance(ControllerMetricsStats.class, "sInstance", null,
+                mMockControllerMetricsStats);
+        replaceInstance(ProvisionMetricsStats.class, "sInstance", null,
+                mMockProvisionMetricsStats);
+        replaceInstance(SessionMetricsStats.class, "sInstance", null,
+                mMockSessionMetricsStats);
+
+        mSharedPreferences = new TestSharedPreferences();
+        when(mContext.getSharedPreferences(anyString(), anyInt())).thenReturn(mSharedPreferences);
+        doReturn(mIsSatelliteServiceSupported)
+                .when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        setUpResponseForRequestSatelliteCapabilities(
+                mSatelliteCapabilities, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RADIO_NOT_AVAILABLE);
+        doNothing().when(mMockDatagramController).setDemoMode(anyBoolean());
+        doNothing().when(mMockSatelliteSessionController)
+                .onSatelliteEnabledStateChanged(anyBoolean());
+        doNothing().when(mMockSatelliteSessionController).setDemoMode(anyBoolean());
+        doNothing().when(mMockControllerMetricsStats).onSatelliteEnabled();
+        doNothing().when(mMockControllerMetricsStats).reportServiceEnablementSuccessCount();
+        doNothing().when(mMockControllerMetricsStats).reportServiceEnablementFailCount();
+        doReturn(mMockSessionMetricsStats)
+                .when(mMockSessionMetricsStats).setInitializationResult(anyInt());
+        doReturn(mMockSessionMetricsStats)
+                .when(mMockSessionMetricsStats).setRadioTechnology(anyInt());
+        doNothing().when(mMockSessionMetricsStats).reportSessionMetrics();
+
+        doReturn(mMockProvisionMetricsStats).when(mMockProvisionMetricsStats)
+                .setResultCode(anyInt());
+        doReturn(mMockProvisionMetricsStats).when(mMockProvisionMetricsStats)
+                    .setIsProvisionRequest(eq(false));
+        doNothing().when(mMockProvisionMetricsStats).reportProvisionMetrics();
+        doNothing().when(mMockControllerMetricsStats).reportDeprovisionCount(anyInt());
+        mSatelliteControllerUT = new TestSatelliteController(mContext, Looper.myLooper());
+        verify(mMockSatelliteModemInterface).registerForSatelliteProvisionStateChanged(
+                any(Handler.class),
+                eq(26) /* EVENT_SATELLITE_PROVISION_STATE_CHANGED */,
+                eq(null));
+        verify(mMockSatelliteModemInterface).registerForPendingDatagrams(
+                any(Handler.class),
+                eq(27) /* EVENT_PENDING_DATAGRAMS */,
+                eq(null));
+        verify(mMockSatelliteModemInterface).registerForSatelliteModemStateChanged(
+                any(Handler.class),
+                eq(28) /* EVENT_SATELLITE_MODEM_STATE_CHANGED */,
+                eq(null));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        logd(TAG + " tearDown");
+        mSatelliteControllerUT = null;
+        super.tearDown();
+    }
+
+    @Test
+    public void testRequestIsSatelliteCommunicationAllowedForCurrentLocation() {
+        mSatelliteAllowedSemaphore.drainPermits();
+        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(false, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.requestIsSatelliteCommunicationAllowedForCurrentLocation(SUB_ID,
+                mSatelliteAllowedReceiver);
+        processAllMessages();
+        assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(1));
+        assertEquals(SATELLITE_NOT_SUPPORTED, mQueriedSatelliteAllowedResultCode);
+
+        resetSatelliteControllerUT();
+        mSatelliteControllerUT.requestIsSatelliteCommunicationAllowedForCurrentLocation(SUB_ID,
+                mSatelliteAllowedReceiver);
+        processAllMessages();
+        assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(1));
+        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, mQueriedSatelliteAllowedResultCode);
+
+        resetSatelliteControllerUT();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteAllowedForCurrentLocation(true, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.requestIsSatelliteCommunicationAllowedForCurrentLocation(SUB_ID,
+                mSatelliteAllowedReceiver);
+        processAllMessages();
+        assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(1));
+        assertEquals(SATELLITE_ERROR_NONE, mQueriedSatelliteAllowedResultCode);
+        assertTrue(mQueriedSatelliteAllowed);
+
+        resetSatelliteControllerUT();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpNullResponseForRequestIsSatelliteAllowedForCurrentLocation(SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.requestIsSatelliteCommunicationAllowedForCurrentLocation(SUB_ID,
+                mSatelliteAllowedReceiver);
+        processAllMessages();
+        assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(1));
+        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, mQueriedSatelliteAllowedResultCode);
+
+        resetSatelliteControllerUT();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpNullResponseForRequestIsSatelliteAllowedForCurrentLocation(
+                SATELLITE_INVALID_MODEM_STATE);
+        mSatelliteControllerUT.requestIsSatelliteCommunicationAllowedForCurrentLocation(SUB_ID,
+                mSatelliteAllowedReceiver);
+        processAllMessages();
+        assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(1));
+        assertEquals(SATELLITE_INVALID_MODEM_STATE, mQueriedSatelliteAllowedResultCode);
+    }
+
+    @Test
+    public void testRequestTimeForNextSatelliteVisibility() {
+        mSatelliteVisibilityTimeSemaphore.drainPermits();
+        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(false, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID,
+                mSatelliteVisibilityTimeReceiver);
+        processAllMessages();
+        assertTrue(waitForRequestTimeForNextSatelliteVisibilityResult(1));
+        assertEquals(SATELLITE_NOT_SUPPORTED, mQueriedSatelliteVisibilityTimeResultCode);
+
+        resetSatelliteControllerUT();
+        mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID,
+                mSatelliteVisibilityTimeReceiver);
+        processAllMessages();
+        assertTrue(waitForRequestTimeForNextSatelliteVisibilityResult(1));
+        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, mQueriedSatelliteVisibilityTimeResultCode);
+
+        resetSatelliteControllerUT();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestTimeForNextSatelliteVisibility(mSatelliteNextVisibilityTime,
+                SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID,
+                mSatelliteVisibilityTimeReceiver);
+        processAllMessages();
+        assertTrue(waitForRequestTimeForNextSatelliteVisibilityResult(1));
+        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, mQueriedSatelliteVisibilityTimeResultCode);
+
+        resetSatelliteControllerUT();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE);
+        verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestTimeForNextSatelliteVisibility(mSatelliteNextVisibilityTime,
+                SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID,
+                mSatelliteVisibilityTimeReceiver);
+        processAllMessages();
+        assertTrue(waitForRequestTimeForNextSatelliteVisibilityResult(1));
+        assertEquals(SATELLITE_SERVICE_NOT_PROVISIONED, mQueriedSatelliteVisibilityTimeResultCode);
+
+        resetSatelliteControllerUT();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestTimeForNextSatelliteVisibility(mSatelliteNextVisibilityTime,
+                SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID,
+                mSatelliteVisibilityTimeReceiver);
+        processAllMessages();
+        assertTrue(waitForRequestTimeForNextSatelliteVisibilityResult(1));
+        assertEquals(SATELLITE_ERROR_NONE, mQueriedSatelliteVisibilityTimeResultCode);
+        assertEquals(mSatelliteNextVisibilityTime, mQueriedSatelliteVisibilityTime);
+
+        resetSatelliteControllerUT();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        setUpNullResponseForRequestTimeForNextSatelliteVisibility(
+                SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID,
+                mSatelliteVisibilityTimeReceiver);
+        processAllMessages();
+        assertTrue(waitForRequestTimeForNextSatelliteVisibilityResult(1));
+        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, mQueriedSatelliteVisibilityTimeResultCode);
+
+        resetSatelliteControllerUT();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        setUpNullResponseForRequestTimeForNextSatelliteVisibility(
+                SATELLITE_INVALID_MODEM_STATE);
+        mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID,
+                mSatelliteVisibilityTimeReceiver);
+        processAllMessages();
+        assertTrue(waitForRequestTimeForNextSatelliteVisibilityResult(1));
+        assertEquals(SATELLITE_INVALID_MODEM_STATE, mQueriedSatelliteVisibilityTimeResultCode);
+    }
+
+    @Test
+    public void testRequestSatelliteEnabled() {
+        mIsSatelliteEnabledSemaphore.drainPermits();
+
+        // Fail to enable satellite when SatelliteController is not fully loaded yet.
+        mIIntegerConsumerResults.clear();
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0));
+
+        // Fail to enable satellite when the device does not support satellite.
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(false, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_NOT_SUPPORTED, (long) mIIntegerConsumerResults.get(0));
+
+        // Fail to enable satellite when the device is not provisioned yet.
+        mIIntegerConsumerResults.clear();
+        resetSatelliteControllerUT();
+        verify(mMockSatelliteSessionController, times(1)).onSatelliteEnabledStateChanged(eq(false));
+        verify(mMockSatelliteSessionController, times(1)).setDemoMode(eq(false));
+        verify(mMockDatagramController, times(1)).setDemoMode(eq(false));
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE);
+        verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_SERVICE_NOT_PROVISIONED, (long) mIIntegerConsumerResults.get(0));
+
+        sendProvisionedStateChangedEvent(true, null);
+        processAllMessages();
+        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+
+        // Successfully disable satellite when radio is turned off.
+        mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
+        setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_ERROR_NONE);
+        setRadioPower(false);
+        processAllMessages();
+        verifySatelliteEnabled(false, SATELLITE_ERROR_NONE);
+        assertTrue(mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled);
+        assertEquals(
+                SATELLITE_MODE_ENABLED_FALSE, mSatelliteControllerUT.satelliteModeSettingValue);
+        verify(mMockSatelliteSessionController, times(2)).onSatelliteEnabledStateChanged(eq(false));
+        verify(mMockSatelliteSessionController, times(2)).setDemoMode(eq(false));
+        verify(mMockDatagramController, times(2)).setDemoMode(eq(false));
+        verify(mMockControllerMetricsStats, times(1)).onSatelliteDisabled();
+
+        // Fail to enable satellite when radio is off.
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        // Radio is not on, can not enable satellite
+        assertEquals(SATELLITE_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0));
+
+        setRadioPower(true);
+        processAllMessages();
+        verifySatelliteEnabled(false, SATELLITE_ERROR_NONE);
+
+        // Fail to enable satellite with an error response from modem when radio is on.
+        mIIntegerConsumerResults.clear();
+        mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
+        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_INVALID_MODEM_STATE);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(false, SATELLITE_ERROR_NONE);
+        verify(mMockPointingAppController, never()).startPointingUI(anyBoolean());
+        assertFalse(mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled);
+        verify(mMockControllerMetricsStats, times(1)).reportServiceEnablementFailCount();
+
+        // Successfully enable satellite when radio is on.
+        mIIntegerConsumerResults.clear();
+        mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
+        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(true, SATELLITE_ERROR_NONE);
+        assertTrue(mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled);
+        assertEquals(SATELLITE_MODE_ENABLED_TRUE, mSatelliteControllerUT.satelliteModeSettingValue);
+        verify(mMockPointingAppController).startPointingUI(eq(false));
+        verify(mMockSatelliteSessionController, times(1)).onSatelliteEnabledStateChanged(eq(true));
+        verify(mMockSatelliteSessionController, times(3)).setDemoMode(eq(false));
+        verify(mMockDatagramController, times(3)).setDemoMode(eq(false));
+        verify(mMockControllerMetricsStats, times(1)).onSatelliteEnabled();
+        verify(mMockControllerMetricsStats, times(1)).reportServiceEnablementSuccessCount();
+        verify(mMockSessionMetricsStats, times(2)).setInitializationResult(anyInt());
+        verify(mMockSessionMetricsStats, times(2)).setRadioTechnology(anyInt());
+        verify(mMockSessionMetricsStats, times(2)).reportSessionMetrics();
+
+        // Successfully enable satellite when it is already enabled.
+        mIIntegerConsumerResults.clear();
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(true, SATELLITE_ERROR_NONE);
+
+        // Fail to enable satellite with a different demo mode when it is already enabled.
+        mIIntegerConsumerResults.clear();
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, true, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_INVALID_ARGUMENTS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(true, SATELLITE_ERROR_NONE);
+
+        // Disable satellite.
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(false, SATELLITE_ERROR_NONE);
+
+        // Disable satellite when satellite is already disabled.
+        mIIntegerConsumerResults.clear();
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(false, SATELLITE_ERROR_NONE);
+
+        // Disable satellite with a different demo mode when satellite is already disabled.
+        mIIntegerConsumerResults.clear();
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, true, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(false, SATELLITE_ERROR_NONE);
+
+        // Send a second request while the first request in progress
+        mIIntegerConsumerResults.clear();
+        setUpNoResponseForRequestSatelliteEnabled(true, false);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        processAllMessages();
+        assertFalse(waitForIIntegerConsumerResult(1));
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_REQUEST_IN_PROGRESS, (long) mIIntegerConsumerResults.get(0));
+
+        mIIntegerConsumerResults.clear();
+        resetSatelliteControllerUTToSupportedAndProvisionedState();
+        // Should receive callback for the above request when satellite modem is turned off.
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0));
+
+        // Move to satellite-disabling in progress.
+        setUpNoResponseForRequestSatelliteEnabled(false, false);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, mIIntegerConsumer);
+        processAllMessages();
+        assertFalse(waitForIIntegerConsumerResult(1));
+
+        // Disable is in progress. Thus, a new request to enable satellite will be rejected.
+        mIIntegerConsumerResults.clear();
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_ERROR, (long) mIIntegerConsumerResults.get(0));
+
+        mIIntegerConsumerResults.clear();
+        resetSatelliteControllerUTToOffAndProvisionedState();
+        // Should receive callback for the above request when satellite modem is turned off.
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0));
+
+        /**
+         * Make areAllRadiosDisabled return false and move mWaitingForRadioDisabled to true, which
+         * will lead to no response for requestSatelliteEnabled.
+         */
+        mSatelliteControllerUT.allRadiosDisabled = false;
+        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        processAllMessages();
+        assertFalse(waitForIIntegerConsumerResult(1));
+
+        resetSatelliteControllerUTEnabledState();
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, mIIntegerConsumer);
+        processAllMessages();
+        // We should receive 2 callbacks for the above 2 requests.
+        assertTrue(waitForIIntegerConsumerResult(2));
+        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(1));
+
+        resetSatelliteControllerUTToOffAndProvisionedState();
+
+        // Repeat the same test as above but with error response from modem for the second request
+        mSatelliteControllerUT.allRadiosDisabled = false;
+        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        processAllMessages();
+        assertFalse(waitForIIntegerConsumerResult(1));
+
+        resetSatelliteControllerUTEnabledState();
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_NO_RESOURCES);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, mIIntegerConsumer);
+        processAllMessages();
+        // We should receive 2 callbacks for the above 2 requests.
+        assertTrue(waitForIIntegerConsumerResult(2));
+        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_NO_RESOURCES, (long) mIIntegerConsumerResults.get(1));
+        mSatelliteControllerUT.allRadiosDisabled = true;
+    }
+
+    @Test
+    public void testRequestSatelliteCapabilities() {
+        mSatelliteCapabilitiesSemaphore.drainPermits();
+        mSatelliteControllerUT.requestSatelliteCapabilities(SUB_ID, mSatelliteCapabilitiesReceiver);
+        processAllMessages();
+        assertTrue(waitForRequestSatelliteCapabilitiesResult(1));
+        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, mQueriedSatelliteCapabilitiesResultCode);
+
+        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(false, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.requestSatelliteCapabilities(SUB_ID, mSatelliteCapabilitiesReceiver);
+        processAllMessages();
+        assertTrue(waitForRequestSatelliteCapabilitiesResult(1));
+        assertEquals(SATELLITE_NOT_SUPPORTED, mQueriedSatelliteCapabilitiesResultCode);
+
+        resetSatelliteControllerUT();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestSatelliteCapabilities(mSatelliteCapabilities, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.requestSatelliteCapabilities(SUB_ID, mSatelliteCapabilitiesReceiver);
+        processAllMessages();
+        assertTrue(waitForRequestSatelliteCapabilitiesResult(1));
+        assertEquals(SATELLITE_ERROR_NONE, mQueriedSatelliteCapabilitiesResultCode);
+        assertEquals(mSatelliteCapabilities, mQueriedSatelliteCapabilities);
+
+        resetSatelliteControllerUT();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpNullResponseForRequestSatelliteCapabilities(SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.requestSatelliteCapabilities(SUB_ID, mSatelliteCapabilitiesReceiver);
+        processAllMessages();
+        assertTrue(waitForRequestSatelliteCapabilitiesResult(1));
+        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, mQueriedSatelliteCapabilitiesResultCode);
+
+        resetSatelliteControllerUT();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpNullResponseForRequestSatelliteCapabilities(SATELLITE_INVALID_MODEM_STATE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.requestSatelliteCapabilities(SUB_ID, mSatelliteCapabilitiesReceiver);
+        processAllMessages();
+        assertTrue(waitForRequestSatelliteCapabilitiesResult(1));
+        assertEquals(SATELLITE_INVALID_MODEM_STATE, mQueriedSatelliteCapabilitiesResultCode);
+    }
+
+    @Test
+    public void testStartSatelliteTransmissionUpdates() {
+        mIIntegerConsumerSemaphore.drainPermits();
+        mIIntegerConsumerResults.clear();
+        mSatelliteControllerUT.startSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
+                mStartTransmissionUpdateCallback);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0));
+
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(false, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.startSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
+                mStartTransmissionUpdateCallback);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_NOT_SUPPORTED, (long) mIIntegerConsumerResults.get(0));
+
+        resetSatelliteControllerUT();
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.startSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
+                mStartTransmissionUpdateCallback);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0));
+
+        resetSatelliteControllerUT();
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE);
+        setUpResponseForStartSatelliteTransmissionUpdates(SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.startSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
+                mStartTransmissionUpdateCallback);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_SERVICE_NOT_PROVISIONED, (long) mIIntegerConsumerResults.get(0));
+
+        resetSatelliteControllerUT();
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        setUpResponseForStartSatelliteTransmissionUpdates(SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.startSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
+                mStartTransmissionUpdateCallback);
+        verify(mMockPointingAppController).registerForSatelliteTransmissionUpdates(anyInt(),
+                eq(mStartTransmissionUpdateCallback), any());
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
+        verify(mMockPointingAppController).startSatelliteTransmissionUpdates(any(Message.class),
+                any(Phone.class));
+        verify(mMockPointingAppController).setStartedSatelliteTransmissionUpdates(eq(true));
+
+        resetSatelliteControllerUT();
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        setUpResponseForStartSatelliteTransmissionUpdates(SATELLITE_INVALID_TELEPHONY_STATE);
+        mSatelliteControllerUT.startSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
+                mStartTransmissionUpdateCallback);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0));
+        verify(mMockPointingAppController).unregisterForSatelliteTransmissionUpdates(anyInt(),
+                any(),  eq(mStartTransmissionUpdateCallback), any(Phone.class));
+        verify(mMockPointingAppController).setStartedSatelliteTransmissionUpdates(eq(false));
+    }
+
+    @Test
+    public void testStopSatelliteTransmissionUpdates() {
+        mIIntegerConsumerSemaphore.drainPermits();
+        mIIntegerConsumerResults.clear();
+        mSatelliteControllerUT.stopSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
+                mStopTransmissionUpdateCallback);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0));
+
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(false, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.stopSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
+                mStopTransmissionUpdateCallback);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_NOT_SUPPORTED, (long) mIIntegerConsumerResults.get(0));
+
+        resetSatelliteControllerUT();
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.stopSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
+                mStopTransmissionUpdateCallback);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0));
+
+        resetSatelliteControllerUT();
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE);
+        setUpResponseForStopSatelliteTransmissionUpdates(SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.stopSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
+                mStopTransmissionUpdateCallback);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_SERVICE_NOT_PROVISIONED, (long) mIIntegerConsumerResults.get(0));
+
+        resetSatelliteControllerUT();
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        setUpResponseForStopSatelliteTransmissionUpdates(SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.stopSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
+                mStopTransmissionUpdateCallback);
+        verify(mMockPointingAppController).unregisterForSatelliteTransmissionUpdates(anyInt(),
+                any(),  eq(mStopTransmissionUpdateCallback), any(Phone.class));
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
+        verify(mMockPointingAppController).stopSatelliteTransmissionUpdates(any(Message.class),
+                any(Phone.class));
+
+        resetSatelliteControllerUT();
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        setUpResponseForStopSatelliteTransmissionUpdates(SATELLITE_INVALID_TELEPHONY_STATE);
+        mSatelliteControllerUT.stopSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
+                mStopTransmissionUpdateCallback);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0));
+    }
+
+    @Test
+    public void testRequestIsDemoModeEnabled() {
+        mIsDemoModeEnabledSemaphore.drainPermits();
+        resetSatelliteControllerUT();
+        mSatelliteControllerUT.requestIsDemoModeEnabled(SUB_ID, mIsDemoModeEnabledReceiver);
+        assertTrue(waitForRequestIsDemoModeEnabledResult(1));
+        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, mQueriedIsDemoModeEnabledResultCode);
+        assertFalse(mQueriedIsDemoModeEnabled);
+
+        resetSatelliteControllerUT();
+        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(false, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.requestIsDemoModeEnabled(SUB_ID, mIsDemoModeEnabledReceiver);
+        assertTrue(waitForRequestIsDemoModeEnabledResult(1));
+        assertEquals(SATELLITE_NOT_SUPPORTED, mQueriedIsDemoModeEnabledResultCode);
+        assertFalse(mQueriedIsDemoModeEnabled);
+
+        resetSatelliteControllerUT();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.requestIsDemoModeEnabled(SUB_ID, mIsDemoModeEnabledReceiver);
+        assertTrue(waitForRequestIsDemoModeEnabledResult(1));
+        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, mQueriedIsDemoModeEnabledResultCode);
+        assertFalse(mQueriedIsDemoModeEnabled);
+
+        resetSatelliteControllerUT();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.requestIsDemoModeEnabled(SUB_ID, mIsDemoModeEnabledReceiver);
+        assertTrue(waitForRequestIsDemoModeEnabledResult(1));
+        assertEquals(SATELLITE_SERVICE_NOT_PROVISIONED, mQueriedIsDemoModeEnabledResultCode);
+        assertFalse(mQueriedIsDemoModeEnabled);
+
+        resetSatelliteControllerUT();
+        boolean isDemoModeEnabled = mSatelliteControllerUT.isDemoModeEnabled();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.requestIsDemoModeEnabled(SUB_ID, mIsDemoModeEnabledReceiver);
+        assertTrue(waitForRequestIsDemoModeEnabledResult(1));
+        assertEquals(SATELLITE_ERROR_NONE, mQueriedIsDemoModeEnabledResultCode);
+        assertEquals(isDemoModeEnabled, mQueriedIsDemoModeEnabled);
+    }
+
+    @Test
+    public void testIsSatelliteEnabled() {
+        assertFalse(mSatelliteControllerUT.isSatelliteEnabled());
+        setUpResponseForRequestIsSatelliteEnabled(true, SATELLITE_ERROR_NONE);
+        mIsSatelliteEnabledSemaphore.drainPermits();
+        mSatelliteControllerUT.requestIsSatelliteEnabled(SUB_ID, mIsSatelliteEnabledReceiver);
+        processAllMessages();
+        assertTrue(waitForRequestIsSatelliteEnabledResult(1));
+        assertEquals(mSatelliteControllerUT.isSatelliteEnabled(), mQueriedIsSatelliteEnabled);
+    }
+
+    @Test
+    public void testOnSatelliteServiceConnected() {
+        verifySatelliteSupported(false, SATELLITE_RADIO_NOT_AVAILABLE);
+        verifySatelliteEnabled(false, SATELLITE_INVALID_TELEPHONY_STATE);
+        verifySatelliteProvisioned(false, SATELLITE_INVALID_TELEPHONY_STATE);
+
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_ERROR_NONE);
+
+        mSatelliteControllerUT.onSatelliteServiceConnected();
+        processAllMessages();
+
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteEnabled(false, SATELLITE_ERROR_NONE);
+        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+    }
+
+    @Test
+    public void testRegisterForSatelliteModemStateChanged() {
+        ISatelliteStateCallback callback = new ISatelliteStateCallback.Stub() {
+            @Override
+            public void onSatelliteModemStateChanged(int state) {
+                logd("onSatelliteModemStateChanged: state=" + state);
+            }
+        };
+        int errorCode = mSatelliteControllerUT.registerForSatelliteModemStateChanged(
+                SUB_ID, callback);
+        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, errorCode);
+        verify(mMockSatelliteSessionController, never())
+                .registerForSatelliteModemStateChanged(callback);
+
+        resetSatelliteControllerUTToSupportedAndProvisionedState();
+
+        errorCode = mSatelliteControllerUT.registerForSatelliteModemStateChanged(
+                SUB_ID, callback);
+        assertEquals(SATELLITE_ERROR_NONE, errorCode);
+        verify(mMockSatelliteSessionController).registerForSatelliteModemStateChanged(callback);
+    }
+
+    @Test
+    public void testUnregisterForSatelliteModemStateChanged() {
+        ISatelliteStateCallback callback = new ISatelliteStateCallback.Stub() {
+            @Override
+            public void onSatelliteModemStateChanged(int state) {
+                logd("onSatelliteModemStateChanged: state=" + state);
+            }
+        };
+        mSatelliteControllerUT.unregisterForSatelliteModemStateChanged(SUB_ID, callback);
+        verify(mMockSatelliteSessionController, never())
+                .unregisterForSatelliteModemStateChanged(callback);
+
+        resetSatelliteControllerUTToSupportedAndProvisionedState();
+
+        mSatelliteControllerUT.unregisterForSatelliteModemStateChanged(SUB_ID, callback);
+        verify(mMockSatelliteSessionController).unregisterForSatelliteModemStateChanged(callback);
+    }
+
+    @Test
+    public void testRegisterForSatelliteProvisionStateChanged() {
+        Semaphore semaphore = new Semaphore(0);
+        ISatelliteProvisionStateCallback callback =
+                new ISatelliteProvisionStateCallback.Stub() {
+                    @Override
+                    public void onSatelliteProvisionStateChanged(boolean provisioned) {
+                        logd("onSatelliteProvisionStateChanged: provisioned=" + provisioned);
+                        try {
+                            semaphore.release();
+                        } catch (Exception ex) {
+                            loge("onSatelliteProvisionStateChanged: Got exception in releasing "
+                                    + "semaphore, ex=" + ex);
+                        }
+                    }
+                };
+        int errorCode = mSatelliteControllerUT.registerForSatelliteProvisionStateChanged(
+                SUB_ID, callback);
+        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, errorCode);
+
+        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(false, SATELLITE_ERROR_NONE);
+        errorCode = mSatelliteControllerUT.registerForSatelliteProvisionStateChanged(
+                SUB_ID, callback);
+        assertEquals(SATELLITE_NOT_SUPPORTED, errorCode);
+
+        resetSatelliteControllerUT();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        errorCode = mSatelliteControllerUT.registerForSatelliteProvisionStateChanged(
+                SUB_ID, callback);
+        assertEquals(SATELLITE_ERROR_NONE, errorCode);
+
+        sendProvisionedStateChangedEvent(true, null);
+        processAllMessages();
+        assertTrue(waitForForEvents(
+                semaphore, 1, "testRegisterForSatelliteProvisionStateChanged"));
+
+        mSatelliteControllerUT.unregisterForSatelliteProvisionStateChanged(SUB_ID, callback);
+        sendProvisionedStateChangedEvent(true, null);
+        processAllMessages();
+        assertFalse(waitForForEvents(
+                semaphore, 1, "testRegisterForSatelliteProvisionStateChanged"));
+    }
+
+    @Test
+    public void testRegisterForSatelliteDatagram() {
+        ISatelliteDatagramCallback callback =
+                new ISatelliteDatagramCallback.Stub() {
+                    @Override
+                    public void onSatelliteDatagramReceived(long datagramId,
+                            @NonNull SatelliteDatagram datagram, int pendingCount,
+                            @NonNull IVoidConsumer internalAck) {
+                        logd("onSatelliteDatagramReceived");
+                    }
+                };
+        when(mMockDatagramController.registerForSatelliteDatagram(eq(SUB_ID), eq(callback)))
+                .thenReturn(SATELLITE_ERROR_NONE);
+        int errorCode = mSatelliteControllerUT.registerForSatelliteDatagram(SUB_ID, callback);
+        assertEquals(SATELLITE_ERROR_NONE, errorCode);
+        verify(mMockDatagramController).registerForSatelliteDatagram(eq(SUB_ID), eq(callback));
+    }
+
+    @Test
+    public void testUnregisterForSatelliteDatagram() {
+        ISatelliteDatagramCallback callback =
+                new ISatelliteDatagramCallback.Stub() {
+                    @Override
+                    public void onSatelliteDatagramReceived(long datagramId,
+                            @NonNull SatelliteDatagram datagram, int pendingCount,
+                            @NonNull IVoidConsumer internalAck) {
+                        logd("onSatelliteDatagramReceived");
+                    }
+                };
+        doNothing().when(mMockDatagramController)
+                .unregisterForSatelliteDatagram(eq(SUB_ID), eq(callback));
+        mSatelliteControllerUT.unregisterForSatelliteDatagram(SUB_ID, callback);
+        verify(mMockDatagramController).unregisterForSatelliteDatagram(eq(SUB_ID), eq(callback));
+    }
+
+    @Test
+    public void testSendSatelliteDatagram() {
+        String mText = "This is a test datagram message from user";
+        SatelliteDatagram datagram = new SatelliteDatagram(mText.getBytes());
+
+        mIIntegerConsumerResults.clear();
+        mSatelliteControllerUT.sendSatelliteDatagram(SUB_ID,
+                SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE, datagram, true, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0));
+        verify(mMockDatagramController, never()).sendSatelliteDatagram(anyInt(),
+                eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE), eq(datagram), eq(true),
+                any());
+
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        sendProvisionedStateChangedEvent(false, null);
+        processAllMessages();
+        verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.sendSatelliteDatagram(SUB_ID,
+                SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE, datagram, true, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_SERVICE_NOT_PROVISIONED, (long) mIIntegerConsumerResults.get(0));
+        verify(mMockDatagramController, never()).sendSatelliteDatagram(anyInt(),
+                eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE), eq(datagram), eq(true),
+                any());
+
+        mIIntegerConsumerResults.clear();
+        sendProvisionedStateChangedEvent(true, null);
+        processAllMessages();
+        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.sendSatelliteDatagram(SUB_ID,
+                SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE, datagram, true, mIIntegerConsumer);
+        processAllMessages();
+        assertFalse(waitForIIntegerConsumerResult(1));
+        verify(mMockDatagramController, times(1)).sendSatelliteDatagram(anyInt(),
+                eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE), eq(datagram), eq(true),
+                any());
+        verify(mMockPointingAppController, times(1)).startPointingUI(eq(true));
+    }
+
+    @Test
+    public void testPollPendingSatelliteDatagrams() {
+        mIIntegerConsumerResults.clear();
+        mSatelliteControllerUT.pollPendingSatelliteDatagrams(SUB_ID, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0));
+        verify(mMockDatagramController, never()).pollPendingSatelliteDatagrams(anyInt(), any());
+
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        sendProvisionedStateChangedEvent(false, null);
+        processAllMessages();
+        verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.pollPendingSatelliteDatagrams(SUB_ID, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_SERVICE_NOT_PROVISIONED, (long) mIIntegerConsumerResults.get(0));
+        verify(mMockDatagramController, never()).pollPendingSatelliteDatagrams(anyInt(), any());
+
+        mIIntegerConsumerResults.clear();
+        sendProvisionedStateChangedEvent(true, null);
+        processAllMessages();
+        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.pollPendingSatelliteDatagrams(SUB_ID, mIIntegerConsumer);
+        processAllMessages();
+        assertFalse(waitForIIntegerConsumerResult(1));
+        verify(mMockDatagramController, times(1)).pollPendingSatelliteDatagrams(anyInt(), any());
+    }
+
+    @Test
+    public void testProvisionSatelliteService() {
+        String mText = "This is test provision data.";
+        byte[] testProvisionData = mText.getBytes();
+        CancellationSignal cancellationSignal = new CancellationSignal();
+        ICancellationSignal cancelRemote = null;
+        mIIntegerConsumerResults.clear();
+        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID,
+                TEST_SATELLITE_TOKEN,
+                testProvisionData, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0));
+        assertNull(cancelRemote);
+
+        resetSatelliteControllerUT();
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(false, SATELLITE_ERROR_NONE);
+        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID,
+                TEST_SATELLITE_TOKEN,
+                testProvisionData, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_NOT_SUPPORTED, (long) mIIntegerConsumerResults.get(0));
+        assertNull(cancelRemote);
+
+        resetSatelliteControllerUT();
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID,
+                TEST_SATELLITE_TOKEN,
+                testProvisionData, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
+        assertNull(cancelRemote);
+
+        resetSatelliteControllerUT();
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE);
+        setUpResponseForProvisionSatelliteService(TEST_SATELLITE_TOKEN, testProvisionData,
+                SATELLITE_ERROR_NONE);
+        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID,
+                TEST_SATELLITE_TOKEN,
+                testProvisionData, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
+        assertNotNull(cancelRemote);
+
+        resetSatelliteControllerUT();
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE);
+        setUpResponseForProvisionSatelliteService(TEST_SATELLITE_TOKEN, testProvisionData,
+                SATELLITE_NOT_AUTHORIZED);
+        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID,
+                TEST_SATELLITE_TOKEN,
+                testProvisionData, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_NOT_AUTHORIZED, (long) mIIntegerConsumerResults.get(0));
+        assertNotNull(cancelRemote);
+
+        resetSatelliteControllerUT();
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE);
+        verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE);
+        setUpResponseForProvisionSatelliteService(TEST_NEXT_SATELLITE_TOKEN, testProvisionData,
+                SATELLITE_ERROR_NONE);
+        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID,
+                TEST_NEXT_SATELLITE_TOKEN, testProvisionData, mIIntegerConsumer);
+        cancellationSignal.setRemote(cancelRemote);
+        cancellationSignal.cancel();
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
+        verify(mMockSatelliteModemInterface).deprovisionSatelliteService(
+                eq(TEST_NEXT_SATELLITE_TOKEN), any(Message.class));
+
+        resetSatelliteControllerUT();
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE);
+        verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE);
+        setUpNoResponseForProvisionSatelliteService(TEST_SATELLITE_TOKEN);
+        setUpResponseForProvisionSatelliteService(TEST_NEXT_SATELLITE_TOKEN, testProvisionData,
+                SATELLITE_ERROR_NONE);
+        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID,
+                TEST_SATELLITE_TOKEN,
+                testProvisionData, mIIntegerConsumer);
+        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID,
+                TEST_NEXT_SATELLITE_TOKEN,
+                testProvisionData, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_SERVICE_PROVISION_IN_PROGRESS,
+                (long) mIIntegerConsumerResults.get(0));
+    }
+
+    @Test
+    public void testDeprovisionSatelliteService() {
+        mIIntegerConsumerSemaphore.drainPermits();
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(false, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID,
+                TEST_SATELLITE_TOKEN, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_NOT_SUPPORTED, (long) mIIntegerConsumerResults.get(0));
+
+        resetSatelliteControllerUT();
+        mIIntegerConsumerResults.clear();
+        mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID,
+                 TEST_SATELLITE_TOKEN, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0));
+
+        resetSatelliteControllerUT();
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForDeprovisionSatelliteService(TEST_SATELLITE_TOKEN, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID,
+                TEST_SATELLITE_TOKEN, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_INVALID_TELEPHONY_STATE, (long) mIIntegerConsumerResults.get(0));
+
+        resetSatelliteControllerUT();
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteProvisioned(false, SATELLITE_ERROR_NONE);
+        setUpResponseForDeprovisionSatelliteService(TEST_SATELLITE_TOKEN, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID,
+                TEST_SATELLITE_TOKEN, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
+
+        resetSatelliteControllerUT();
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        setUpResponseForDeprovisionSatelliteService(TEST_SATELLITE_TOKEN, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID,
+                TEST_SATELLITE_TOKEN, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
+
+        resetSatelliteControllerUT();
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+        setUpResponseForDeprovisionSatelliteService(TEST_SATELLITE_TOKEN,
+                SATELLITE_INVALID_MODEM_STATE);
+        mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID,
+                TEST_SATELLITE_TOKEN, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0));
+
+    }
+
+    private void resetSatelliteControllerUTEnabledState() {
+        logd("resetSatelliteControllerUTEnabledState");
+        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RADIO_NOT_AVAILABLE);
+        doReturn(true).when(mMockSatelliteModemInterface)
+                .setSatelliteServicePackageName(anyString());
+        mSatelliteControllerUT.setSatelliteServicePackageName("TestSatelliteService");
+        processAllMessages();
+
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        sendProvisionedStateChangedEvent(true, null);
+        processAllMessages();
+        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+    }
+
+    private void resetSatelliteControllerUT() {
+        logd("resetSatelliteControllerUT");
+        // Trigger cleanUpResources
+        sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_UNAVAILABLE, null);
+        processAllMessages();
+
+        // Reset all cached states
+        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RADIO_NOT_AVAILABLE);
+        doReturn(true).when(mMockSatelliteModemInterface)
+                .setSatelliteServicePackageName(anyString());
+        mSatelliteControllerUT.setSatelliteServicePackageName("TestSatelliteService");
+        processAllMessages();
+    }
+
+    private void resetSatelliteControllerUTToSupportedAndProvisionedState() {
+        resetSatelliteControllerUT();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_ERROR_NONE);
+        verifySatelliteSupported(true, SATELLITE_ERROR_NONE);
+        sendProvisionedStateChangedEvent(true, null);
+        processAllMessages();
+        verifySatelliteProvisioned(true, SATELLITE_ERROR_NONE);
+    }
+
+    private void resetSatelliteControllerUTToOffAndProvisionedState() {
+        resetSatelliteControllerUTToSupportedAndProvisionedState();
+        sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_OFF, null);
+        processAllMessages();
+        verifySatelliteEnabled(false, SATELLITE_ERROR_NONE);
+    }
+
+    private void resetSatelliteControllerUTToOnAndProvisionedState() {
+        resetSatelliteControllerUTToOffAndProvisionedState();
+        setRadioPower(true);
+        processAllMessages();
+
+        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_ERROR_NONE);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_ERROR_NONE, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(true, SATELLITE_ERROR_NONE);
+    }
+
+    private void setUpResponseForRequestIsSatelliteEnabled(boolean isSatelliteEnabled,
+            @SatelliteManager.SatelliteError int error) {
+        SatelliteException exception = (error == SATELLITE_ERROR_NONE)
+                ? null : new SatelliteException(error);
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[0];
+            AsyncResult.forMessage(message, isSatelliteEnabled, exception);
+            message.sendToTarget();
+            return null;
+        }).when(mMockSatelliteModemInterface).requestIsSatelliteEnabled(any(Message.class));
+    }
+
+    private void setUpResponseForRequestIsSatelliteSupported(
+            boolean isSatelliteSupported, @SatelliteManager.SatelliteError int error) {
+        SatelliteException exception = (error == SATELLITE_ERROR_NONE)
+                ? null : new SatelliteException(error);
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[0];
+            AsyncResult.forMessage(message, isSatelliteSupported, exception);
+            message.sendToTarget();
+            return null;
+        }).when(mMockSatelliteModemInterface).requestIsSatelliteSupported(any(Message.class));
+    }
+
+    private void setUpResponseForRequestIsSatelliteAllowedForCurrentLocation(
+            boolean isSatelliteAllowed, @SatelliteManager.SatelliteError int error) {
+        SatelliteException exception = (error == SATELLITE_ERROR_NONE)
+                ? null : new SatelliteException(error);
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[0];
+            AsyncResult.forMessage(message, isSatelliteAllowed, exception);
+            message.sendToTarget();
+            return null;
+        }).when(mMockSatelliteModemInterface)
+                .requestIsSatelliteCommunicationAllowedForCurrentLocation(any(Message.class));
+    }
+
+    private void setUpNullResponseForRequestIsSatelliteAllowedForCurrentLocation(
+            @SatelliteManager.SatelliteError int error) {
+        SatelliteException exception = (error == SATELLITE_ERROR_NONE)
+                ? null : new SatelliteException(error);
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[0];
+            AsyncResult.forMessage(message, null, exception);
+            message.sendToTarget();
+            return null;
+        }).when(mMockSatelliteModemInterface)
+                .requestIsSatelliteCommunicationAllowedForCurrentLocation(any(Message.class));
+    }
+
+    private void setUpResponseForRequestTimeForNextSatelliteVisibility(
+            int satelliteVisibilityTime, @SatelliteManager.SatelliteError int error) {
+        SatelliteException exception = (error == SATELLITE_ERROR_NONE)
+                ? null : new SatelliteException(error);
+        int[] visibilityTime = new int[] {satelliteVisibilityTime};
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[0];
+            AsyncResult.forMessage(message, visibilityTime, exception);
+            message.sendToTarget();
+            return null;
+        }).when(mMockSatelliteModemInterface)
+                .requestTimeForNextSatelliteVisibility(any(Message.class));
+    }
+
+    private void setUpNullResponseForRequestTimeForNextSatelliteVisibility(
+            @SatelliteManager.SatelliteError int error) {
+        SatelliteException exception = (error == SATELLITE_ERROR_NONE)
+                ? null : new SatelliteException(error);
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[0];
+            AsyncResult.forMessage(message, null, exception);
+            message.sendToTarget();
+            return null;
+        }).when(mMockSatelliteModemInterface)
+                .requestTimeForNextSatelliteVisibility(any(Message.class));
+    }
+
+    private void setUpResponseForRequestIsSatelliteProvisioned(
+            boolean isSatelliteProvisioned, @SatelliteManager.SatelliteError int error) {
+        SatelliteException exception = (error == SATELLITE_ERROR_NONE)
+                ? null : new SatelliteException(error);
+        int[] provisioned = new int[] {isSatelliteProvisioned ? 1 : 0};
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[0];
+            AsyncResult.forMessage(message, provisioned, exception);
+            message.sendToTarget();
+            return null;
+        }).when(mMockSatelliteModemInterface).requestIsSatelliteProvisioned(any(Message.class));
+    }
+
+    private void setUpResponseForRequestSatelliteEnabled(
+            boolean enabled, boolean demoMode, @SatelliteManager.SatelliteError int error) {
+        SatelliteException exception = (error == SATELLITE_ERROR_NONE)
+                ? null : new SatelliteException(error);
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[2];
+            AsyncResult.forMessage(message, null, exception);
+            message.sendToTarget();
+            return null;
+        }).when(mMockSatelliteModemInterface)
+                .requestSatelliteEnabled(eq(enabled), eq(demoMode), any(Message.class));
+    }
+
+    private void setUpNoResponseForRequestSatelliteEnabled(boolean enabled, boolean demoMode) {
+        doNothing().when(mMockSatelliteModemInterface)
+                .requestSatelliteEnabled(eq(enabled), eq(demoMode), any(Message.class));
+    }
+
+    private void setUpResponseForProvisionSatelliteService(
+            String token, byte[] provisionData, @SatelliteManager.SatelliteError int error) {
+        SatelliteException exception = (error == SATELLITE_ERROR_NONE)
+                ? null : new SatelliteException(error);
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[2];
+            AsyncResult.forMessage(message, null, exception);
+            message.sendToTarget();
+            return null;
+        }).when(mMockSatelliteModemInterface)
+                .provisionSatelliteService(eq(token), any(byte[].class), any(Message.class));
+    }
+
+    private void setUpNoResponseForProvisionSatelliteService(String token) {
+        doNothing().when(mMockSatelliteModemInterface)
+                .provisionSatelliteService(eq(token), any(), any(Message.class));
+    }
+
+    private void setUpResponseForDeprovisionSatelliteService(String token,
+            @SatelliteManager.SatelliteError int error) {
+        SatelliteException exception = (error == SATELLITE_ERROR_NONE)
+                ? null : new SatelliteException(error);
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[1];
+            AsyncResult.forMessage(message, null, exception);
+            message.sendToTarget();
+            return null;
+        }).when(mMockSatelliteModemInterface)
+                .deprovisionSatelliteService(eq(token), any(Message.class));
+    }
+
+    private void setUpResponseForRequestSatelliteCapabilities(
+            SatelliteCapabilities satelliteCapabilities,
+            @SatelliteManager.SatelliteError int error) {
+        SatelliteException exception = (error == SATELLITE_ERROR_NONE)
+                ? null : new SatelliteException(error);
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[0];
+            AsyncResult.forMessage(message, satelliteCapabilities, exception);
+            message.sendToTarget();
+            return null;
+        }).when(mMockSatelliteModemInterface).requestSatelliteCapabilities(any(Message.class));
+    }
+
+    private boolean waitForForEvents(
+            Semaphore semaphore, int expectedNumberOfEvents, String caller) {
+        for (int i = 0; i < expectedNumberOfEvents; i++) {
+            try {
+                if (!semaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) {
+                    loge(caller + ": Timeout to receive the expected event");
+                    return false;
+                }
+            } catch (Exception ex) {
+                loge(caller + ": Got exception=" + ex);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private void setUpNullResponseForRequestSatelliteCapabilities(
+            @SatelliteManager.SatelliteError int error) {
+        SatelliteException exception = (error == SATELLITE_ERROR_NONE)
+                ? null : new SatelliteException(error);
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[0];
+            AsyncResult.forMessage(message, null, exception);
+            message.sendToTarget();
+            return null;
+        }).when(mMockSatelliteModemInterface).requestSatelliteCapabilities(any(Message.class));
+    }
+
+    private void setUpResponseForStartSatelliteTransmissionUpdates(
+            @SatelliteManager.SatelliteError int error) {
+        SatelliteException exception = (error == SATELLITE_ERROR_NONE)
+                ? null : new SatelliteException(error);
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[0];
+            AsyncResult.forMessage(message, null, exception);
+            message.sendToTarget();
+            return null;
+        }).when(mMockPointingAppController).startSatelliteTransmissionUpdates(any(Message.class),
+                any());
+    }
+
+    private void setUpResponseForStopSatelliteTransmissionUpdates(
+            @SatelliteManager.SatelliteError int error) {
+        SatelliteException exception = (error == SATELLITE_ERROR_NONE)
+                ? null : new SatelliteException(error);
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[0];
+            AsyncResult.forMessage(message, null, exception);
+            message.sendToTarget();
+            return null;
+        }).when(mMockPointingAppController).stopSatelliteTransmissionUpdates(any(Message.class),
+                any());
+    }
+
+    private boolean waitForRequestIsSatelliteSupportedResult(int expectedNumberOfEvents) {
+        for (int i = 0; i < expectedNumberOfEvents; i++) {
+            try {
+                if (!mSatelliteSupportSemaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) {
+                    loge("Timeout to receive requestIsSatelliteSupported() callback");
+                    return false;
+                }
+            } catch (Exception ex) {
+                loge("waitForRequestIsSatelliteSupportedResult: Got exception=" + ex);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean waitForRequestIsSatelliteAllowedForCurrentLocationResult(
+            int expectedNumberOfEvents) {
+        for (int i = 0; i < expectedNumberOfEvents; i++) {
+            try {
+                if (!mSatelliteAllowedSemaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) {
+                    loge("Timeout to receive "
+                            + "requestIsSatelliteCommunicationAllowedForCurrentLocation()"
+                            + " callback");
+                    return false;
+                }
+            } catch (Exception ex) {
+                loge("waitForRequestIsSatelliteSupportedResult: Got exception=" + ex);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean waitForRequestTimeForNextSatelliteVisibilityResult(
+            int expectedNumberOfEvents) {
+        for (int i = 0; i < expectedNumberOfEvents; i++) {
+            try {
+                if (!mSatelliteVisibilityTimeSemaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) {
+                    loge("Timeout to receive "
+                            + "requestTimeForNextSatelliteVisibility() callback");
+                    return false;
+                }
+            } catch (Exception ex) {
+                loge("waitForRequestTimeForNextSatelliteVisibilityResult: Got exception=" + ex);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean waitForRequestIsSatelliteEnabledResult(int expectedNumberOfEvents) {
+        for (int i = 0; i < expectedNumberOfEvents; i++) {
+            try {
+                if (!mIsSatelliteEnabledSemaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) {
+                    loge("Timeout to receive requestIsSatelliteEnabled() callback");
+                    return false;
+                }
+            } catch (Exception ex) {
+                loge("waitForRequestIsSatelliteEnabledResult: Got exception=" + ex);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean waitForRequestIsSatelliteProvisionedResult(int expectedNumberOfEvents) {
+        for (int i = 0; i < expectedNumberOfEvents; i++) {
+            try {
+                if (!mIsSatelliteProvisionedSemaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) {
+                    loge("Timeout to receive requestIsSatelliteProvisioned() callback");
+                    return false;
+                }
+            } catch (Exception ex) {
+                loge("waitForRequestIsSatelliteProvisionedResult: Got exception=" + ex);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean waitForRequestSatelliteCapabilitiesResult(int expectedNumberOfEvents) {
+        for (int i = 0; i < expectedNumberOfEvents; i++) {
+            try {
+                if (!mSatelliteCapabilitiesSemaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) {
+                    loge("Timeout to receive requestSatelliteCapabilities() callback");
+                    return false;
+                }
+            } catch (Exception ex) {
+                loge("waitForRequestSatelliteCapabilitiesResult: Got exception=" + ex);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean waitForRequestIsDemoModeEnabledResult(int expectedNumberOfEvents) {
+        for (int i = 0; i < expectedNumberOfEvents; i++) {
+            try {
+                if (!mIsDemoModeEnabledSemaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) {
+                    loge("Timeout to receive requestIsDemoModeEnabled() callback");
+                    return false;
+                }
+            } catch (Exception ex) {
+                loge("waitForRequestIsDemoModeEnabled: Got exception=" + ex);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean waitForIIntegerConsumerResult(int expectedNumberOfEvents) {
+        for (int i = 0; i < expectedNumberOfEvents; i++) {
+            try {
+                if (!mIIntegerConsumerSemaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) {
+                    loge("Timeout to receive IIntegerConsumer() callback");
+                    return false;
+                }
+            } catch (Exception ex) {
+                loge("waitForIIntegerConsumerResult: Got exception=" + ex);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private void verifySatelliteSupported(boolean supported, int expectedErrorCode) {
+        mSatelliteSupportSemaphore.drainPermits();
+        mSatelliteControllerUT.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);
+        processAllMessages();
+        assertTrue(waitForRequestIsSatelliteEnabledResult(1));
+        assertEquals(expectedErrorCode, mQueriedIsSatelliteEnabledResultCode);
+        assertEquals(enabled, mQueriedIsSatelliteEnabled);
+    }
+
+    private void verifySatelliteProvisioned(boolean provisioned, int expectedErrorCode) {
+        mIsSatelliteProvisionedSemaphore.drainPermits();
+        mSatelliteControllerUT.requestIsSatelliteProvisioned(
+                SUB_ID, mIsSatelliteProvisionedReceiver);
+        processAllMessages();
+        assertTrue(waitForRequestIsSatelliteProvisionedResult(1));
+        assertEquals(expectedErrorCode, mQueriedIsSatelliteProvisionedResultCode);
+        assertEquals(provisioned, mQueriedIsSatelliteProvisioned);
+    }
+
+    private void sendProvisionedStateChangedEvent(boolean provisioned, Throwable exception) {
+        Message msg = mSatelliteControllerUT.obtainMessage(
+                26 /* EVENT_SATELLITE_PROVISION_STATE_CHANGED */);
+        msg.obj = new AsyncResult(null, provisioned, exception);
+        msg.sendToTarget();
+    }
+
+    private void sendSatelliteModemStateChangedEvent(int state, Throwable exception) {
+        Message msg = mSatelliteControllerUT.obtainMessage(
+                28 /* EVENT_SATELLITE_MODEM_STATE_CHANGED */);
+        msg.obj = new AsyncResult(null, state, exception);
+        msg.sendToTarget();
+    }
+
+    private void setRadioPower(boolean on) {
+        mSimulatedCommands.setRadioPower(on, false, false, null);
+    }
+
+    private static void loge(String message) {
+        Rlog.e(TAG, message);
+    }
+
+    private static class TestSharedPreferences
+            implements SharedPreferences, SharedPreferences.Editor {
+        private HashMap<String, Object> mValues = new HashMap<String, Object>();
+
+        public int getValueCount() {
+            return mValues.size();
+        }
+
+        @Override
+        public Editor edit() {
+            return this;
+        }
+
+        @Override
+        public boolean contains(String key) {
+            return mValues.containsKey(key);
+        }
+
+        @Override
+        public Map<String, ?> getAll() {
+            return new HashMap<String, Object>(mValues);
+        }
+
+        @Override
+        public boolean getBoolean(String key, boolean defValue) {
+            if (mValues.containsKey(key)) {
+                return ((Boolean) mValues.get(key)).booleanValue();
+            }
+            return defValue;
+        }
+
+        @Override
+        public float getFloat(String key, float defValue) {
+            if (mValues.containsKey(key)) {
+                return ((Float) mValues.get(key)).floatValue();
+            }
+            return defValue;
+        }
+
+        @Override
+        public int getInt(String key, int defValue) {
+            if (mValues.containsKey(key)) {
+                return ((Integer) mValues.get(key)).intValue();
+            }
+            return defValue;
+        }
+
+        @Override
+        public long getLong(String key, long defValue) {
+            if (mValues.containsKey(key)) {
+                return ((Long) mValues.get(key)).longValue();
+            }
+            return defValue;
+        }
+
+        @Override
+        public String getString(String key, String defValue) {
+            if (mValues.containsKey(key)) return (String) mValues.get(key);
+            else return defValue;
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public Set<String> getStringSet(String key, Set<String> defValues) {
+            if (mValues.containsKey(key)) {
+                return (Set<String>) mValues.get(key);
+            }
+            return defValues;
+        }
+
+        @Override
+        public void registerOnSharedPreferenceChangeListener(
+                OnSharedPreferenceChangeListener listener) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void unregisterOnSharedPreferenceChangeListener(
+                OnSharedPreferenceChangeListener listener) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Editor putBoolean(String key, boolean value) {
+            mValues.put(key, Boolean.valueOf(value));
+            return this;
+        }
+
+        @Override
+        public Editor putFloat(String key, float value) {
+            mValues.put(key, value);
+            return this;
+        }
+
+        @Override
+        public Editor putInt(String key, int value) {
+            mValues.put(key, value);
+            return this;
+        }
+
+        @Override
+        public Editor putLong(String key, long value) {
+            mValues.put(key, value);
+            return this;
+        }
+
+        @Override
+        public Editor putString(String key, String value) {
+            mValues.put(key, value);
+            return this;
+        }
+
+        @Override
+        public Editor putStringSet(String key, Set<String> values) {
+            mValues.put(key, values);
+            return this;
+        }
+
+        @Override
+        public Editor remove(String key) {
+            mValues.remove(key);
+            return this;
+        }
+
+        @Override
+        public Editor clear() {
+            mValues.clear();
+            return this;
+        }
+
+        @Override
+        public boolean commit() {
+            return true;
+        }
+
+        @Override
+        public void apply() {
+            commit();
+        }
+    }
+
+    private static class TestSatelliteController extends SatelliteController {
+        public boolean setSettingsKeyForSatelliteModeCalled = false;
+        public boolean allRadiosDisabled = true;
+        public int satelliteModeSettingValue = SATELLITE_MODE_ENABLED_FALSE;
+
+        TestSatelliteController(Context context, Looper looper) {
+            super(context, looper);
+            logd("Constructing TestSatelliteController");
+        }
+
+        @Override
+        protected void initializeSatelliteModeRadios() {
+            logd("initializeSatelliteModeRadios");
+        }
+
+        @Override
+        protected void setSettingsKeyForSatelliteMode(int val) {
+            logd("setSettingsKeyForSatelliteMode: val=" + val);
+            satelliteModeSettingValue = val;
+            setSettingsKeyForSatelliteModeCalled = true;
+        }
+
+        @Override
+        protected boolean areAllRadiosDisabled() {
+            return allRadiosDisabled;
+        }
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java
new file mode 100644
index 0000000..418d0aa
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java
@@ -0,0 +1,602 @@
+/*
+ * 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.satellite;
+
+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.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.telecom.Call;
+import android.telecom.Connection;
+import android.telephony.BinderCacheManager;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.telephony.ims.RegistrationManager;
+import android.telephony.satellite.ISatelliteProvisionStateCallback;
+import android.telephony.satellite.SatelliteManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.Log;
+
+import com.android.ims.ImsException;
+import com.android.ims.ImsManager;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/**
+ * Unit tests for SatelliteSOSMessageRecommender
+ */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class SatelliteSOSMessageRecommenderTest extends TelephonyTest {
+    private static final String TAG = "SatelliteSOSMessageRecommenderTest";
+    private static final long TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS = 500;
+    private static final int PHONE_ID = 0;
+    private static final String CALL_ID = "CALL_ID";
+    private static final String WRONG_CALL_ID = "WRONG_CALL_ID";
+    private TestSatelliteController mTestSatelliteController;
+    private TestImsManager mTestImsManager;
+
+    @Mock
+    private Context mMockContext;
+    @Mock
+    private Resources mResources;
+    @Mock
+    private ImsManager.MmTelFeatureConnectionFactory mMmTelFeatureConnectionFactory;
+    private TestConnection mTestConnection;
+    private TestSOSMessageRecommender mTestSOSMessageRecommender;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        MockitoAnnotations.initMocks(this);
+
+        when(mMockContext.getMainLooper()).thenReturn(Looper.myLooper());
+        when(mMockContext.getResources()).thenReturn(mResources);
+        when(mResources.getString(com.android.internal.R.string.config_satellite_service_package))
+                .thenReturn("");
+        mTestSatelliteController = new TestSatelliteController(mMockContext,
+                Looper.myLooper());
+        mTestImsManager = new TestImsManager(
+                mMockContext, PHONE_ID, mMmTelFeatureConnectionFactory, null, null, null);
+        mTestConnection = new TestConnection(CALL_ID);
+        when(mPhone.getServiceState()).thenReturn(mServiceState);
+        mTestSOSMessageRecommender = new TestSOSMessageRecommender(Looper.myLooper(),
+                mTestSatelliteController, mTestImsManager,
+                TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS);
+        when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+        when(mPhone.isImsRegistered()).thenReturn(false);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    public void testTimeoutBeforeEmergencyCallEnd() {
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone);
+        processAllMessages();
+        assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
+
+        // Wait for the timeout to expires
+        moveTimeForward(TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS);
+        processAllMessages();
+
+        assertRegisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+        assertTrue(mTestConnection.isEventSent(Call.EVENT_DISPLAY_SOS_MESSAGE));
+        assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+    }
+
+    @Test
+    public void testStopTrackingCallBeforeTimeout_ConnectionActive() {
+        testStopTrackingCallBeforeTimeout(Connection.STATE_ACTIVE);
+    }
+
+    @Test
+    public void testStopTrackingCallBeforeTimeout_ConnectionDisconnected() {
+        testStopTrackingCallBeforeTimeout(Connection.STATE_DISCONNECTED);
+    }
+
+    @Test
+    public void testImsRegistrationStateChangedBeforeTimeout() {
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone);
+        processAllMessages();
+
+        assertTrue(mTestSOSMessageRecommender.isTimerStarted());
+        assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        assertRegisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+
+        mTestImsManager.sendImsRegistrationStateChangedEvent(true);
+        processAllMessages();
+
+        assertFalse(mTestConnection.isEventSent(Call.EVENT_DISPLAY_SOS_MESSAGE));
+        assertFalse(mTestSOSMessageRecommender.isTimerStarted());
+        assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        assertUnregisterForStateChangedEventsTriggered(mPhone, 0, 0, 0);
+
+        mTestImsManager.sendImsRegistrationStateChangedEvent(false);
+        processAllMessages();
+        assertEquals(2, mTestSOSMessageRecommender.getCountOfTimerStarted());
+
+        // Wait for the timeout to expires
+        moveTimeForward(TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS);
+        processAllMessages();
+
+        assertTrue(mTestConnection.isEventSent(Call.EVENT_DISPLAY_SOS_MESSAGE));
+        assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+        assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
+    }
+
+    @Test
+    public void testSatelliteProvisionStateChangedBeforeTimeout() {
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone);
+        processAllMessages();
+
+        assertTrue(mTestSOSMessageRecommender.isTimerStarted());
+        assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        assertRegisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+
+        mTestSatelliteController.sendProvisionStateChangedEvent(
+                SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, false);
+        processAllMessages();
+
+        assertFalse(mTestSOSMessageRecommender.isTimerStarted());
+        assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone);
+        processAllMessages();
+        assertTrue(mTestSOSMessageRecommender.isTimerStarted());
+        assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        assertRegisterForStateChangedEventsTriggered(mPhone, 2, 2, 2);
+
+        mTestSatelliteController.sendProvisionStateChangedEvent(
+                SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, true);
+
+        // Wait for the timeout to expires
+        moveTimeForward(TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS);
+        processAllMessages();
+
+        assertTrue(mTestConnection.isEventSent(Call.EVENT_DISPLAY_SOS_MESSAGE));
+        assertFalse(mTestSOSMessageRecommender.isTimerStarted());
+        assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        assertUnregisterForStateChangedEventsTriggered(mPhone, 2, 2, 2);
+    }
+
+    @Test
+    public void testEmergencyCallRedialBeforeTimeout() {
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone);
+        processAllMessages();
+
+        assertTrue(mTestSOSMessageRecommender.isTimerStarted());
+        assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        assertRegisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+
+        Phone newPhone = Mockito.mock(Phone.class);
+        when(newPhone.getServiceState()).thenReturn(mServiceState);
+        when(newPhone.isImsRegistered()).thenReturn(false);
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, newPhone);
+        processAllMessages();
+
+        assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+        assertTrue(mTestSOSMessageRecommender.isTimerStarted());
+        assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        /**
+         * Since {@link SatelliteSOSMessageRecommender} always uses
+         * {@link SubscriptionManager#DEFAULT_SUBSCRIPTION_ID} when registering for provision state
+         * changed events with {@link SatelliteController}, registerForProvisionCount does
+         * not depend on Phone.
+         * <p>
+         * Since we use a single mocked ImsManager instance, registerForImsCount does not depend on
+         * Phone.
+         */
+        assertRegisterForStateChangedEventsTriggered(newPhone, 2, 2, 1);
+
+        // Wait for the timeout to expires
+        moveTimeForward(TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS);
+        processAllMessages();
+
+        assertTrue(mTestConnection.isEventSent(Call.EVENT_DISPLAY_SOS_MESSAGE));
+        /**
+         * Since {@link SatelliteSOSMessageRecommender} always uses
+         * {@link SubscriptionManager#DEFAULT_SUBSCRIPTION_ID} when unregistering for provision
+         * state changed events with {@link SatelliteController}, unregisterForProvisionCount does
+         * not depend on Phone.
+         * <p>
+         * Since we use a single mocked ImsManager instance, unregisterForImsCount does not depend
+         * on Phone.
+         */
+        assertUnregisterForStateChangedEventsTriggered(newPhone, 2, 2, 1);
+        assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
+    }
+
+    @Test
+    public void testCellularServiceStateChangedBeforeTimeout_InServiceToOutOfService() {
+        testCellularServiceStateChangedBeforeTimeout(
+                ServiceState.STATE_IN_SERVICE, ServiceState.STATE_OUT_OF_SERVICE);
+    }
+
+    @Test
+    public void testCellularServiceStateChangedBeforeTimeout_InServiceToPowerOff() {
+        testCellularServiceStateChangedBeforeTimeout(
+                ServiceState.STATE_IN_SERVICE, ServiceState.STATE_POWER_OFF);
+    }
+
+    @Test
+    public void testCellularServiceStateChangedBeforeTimeout_EmergencyOnlyToOutOfService() {
+        testCellularServiceStateChangedBeforeTimeout(
+                ServiceState.STATE_EMERGENCY_ONLY, ServiceState.STATE_OUT_OF_SERVICE);
+    }
+
+    @Test
+    public void testCellularServiceStateChangedBeforeTimeout_EmergencyOnlyToPowerOff() {
+        testCellularServiceStateChangedBeforeTimeout(
+                ServiceState.STATE_EMERGENCY_ONLY, ServiceState.STATE_POWER_OFF);
+    }
+
+    @Test
+    public void testOnEmergencyCallConnectionStateChangedWithWrongCallId() {
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone);
+        processAllMessages();
+
+        assertTrue(mTestSOSMessageRecommender.isTimerStarted());
+        assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        assertRegisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+
+        mTestSOSMessageRecommender.onEmergencyCallConnectionStateChanged(
+                WRONG_CALL_ID, Connection.STATE_ACTIVE);
+        processAllMessages();
+
+        assertFalse(mTestConnection.isEventSent(Call.EVENT_DISPLAY_SOS_MESSAGE));
+        assertFalse(mTestSOSMessageRecommender.isTimerStarted());
+        assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+    }
+
+    @Test
+    public void testSatelliteNotAllowedInCurrentLocation() {
+        mTestSatelliteController.setIsSatelliteCommunicationAllowed(false);
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone);
+        processAllMessages();
+
+        /**
+         * We should have registered for the state change events abd started the timer when
+         * receiving the event onEmergencyCallStarted. After getting the callback for the result of
+         * the request requestIsSatelliteCommunicationAllowedForCurrentLocation, the resources
+         * should be cleaned up.
+         */
+        assertFalse(mTestSOSMessageRecommender.isTimerStarted());
+        assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        assertRegisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+        assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+    }
+
+    @Test
+    public void testOnEmergencyCallStarted() {
+        SatelliteController satelliteController = new SatelliteController(
+                mMockContext, Looper.myLooper());
+        TestSOSMessageRecommender testSOSMessageRecommender = new TestSOSMessageRecommender(
+                Looper.myLooper(),
+                satelliteController, mTestImsManager,
+                TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS);
+        testSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone);
+        processAllMessages();
+
+        assertFalse(testSOSMessageRecommender.isTimerStarted());
+        assertEquals(0, testSOSMessageRecommender.getCountOfTimerStarted());
+    }
+
+    private void testStopTrackingCallBeforeTimeout(
+            @Connection.ConnectionState int connectionState) {
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone);
+        processAllMessages();
+
+        assertTrue(mTestSOSMessageRecommender.isTimerStarted());
+        assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        assertRegisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+
+        mTestSOSMessageRecommender.onEmergencyCallConnectionStateChanged(CALL_ID, connectionState);
+        processAllMessages();
+
+        assertFalse(mTestConnection.isEventSent(Call.EVENT_DISPLAY_SOS_MESSAGE));
+        assertFalse(mTestSOSMessageRecommender.isTimerStarted());
+        assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+    }
+
+    private void testCellularServiceStateChangedBeforeTimeout(
+            @ServiceState.RegState int availableServiceState,
+            @ServiceState.RegState int unavailableServiceState) {
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, mPhone);
+        processAllMessages();
+
+        assertTrue(mTestSOSMessageRecommender.isTimerStarted());
+        assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        assertRegisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+
+        mTestSOSMessageRecommender.sendServiceStateChangedEvent(availableServiceState);
+        processAllMessages();
+
+        assertFalse(mTestConnection.isEventSent(Call.EVENT_DISPLAY_SOS_MESSAGE));
+        assertFalse(mTestSOSMessageRecommender.isTimerStarted());
+        assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        assertUnregisterForStateChangedEventsTriggered(mPhone, 0, 0, 0);
+
+        mTestSOSMessageRecommender.sendServiceStateChangedEvent(unavailableServiceState);
+        processAllMessages();
+        assertEquals(2, mTestSOSMessageRecommender.getCountOfTimerStarted());
+
+        // Wait for the timeout to expires
+        moveTimeForward(TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS);
+        processAllMessages();
+
+        assertTrue(mTestConnection.isEventSent(Call.EVENT_DISPLAY_SOS_MESSAGE));
+        assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1, 1);
+        assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
+    }
+
+    private void assertRegisterForStateChangedEventsTriggered(
+            Phone phone, int registerForProvisionCount, int registerForImsCount,
+            int registerForCellularCount) {
+        assertEquals(registerForProvisionCount,
+                mTestSatelliteController.getRegisterForSatelliteProvisionStateChangedCalls());
+        assertEquals(registerForImsCount, mTestImsManager.getAddRegistrationCallbackCalls());
+        verify(phone, times(registerForCellularCount))
+                .registerForServiceStateChanged(any(), anyInt(), any());
+    }
+
+    private void assertUnregisterForStateChangedEventsTriggered(
+            Phone phone, int unregisterForProvisionCount, int unregisterForImsCount,
+            int unregisterForCellularCount) {
+        assertEquals(unregisterForProvisionCount,
+                mTestSatelliteController.getUnregisterForSatelliteProvisionStateChangedCalls());
+        assertEquals(unregisterForImsCount, mTestImsManager.getRemoveRegistrationListenerCalls());
+        verify(phone, times(unregisterForCellularCount)).unregisterForServiceStateChanged(any());
+    }
+
+    private static class TestSatelliteController extends SatelliteController {
+
+        private static final String TAG = "TestSatelliteController";
+        private final Map<Integer, Set<ISatelliteProvisionStateCallback>>
+                mProvisionStateChangedCallbacks;
+        private int mRegisterForSatelliteProvisionStateChangedCalls = 0;
+        private int mUnregisterForSatelliteProvisionStateChangedCalls = 0;
+        private boolean mIsSatelliteProvisioned = true;
+        private boolean mIsSatelliteCommunicationAllowed = true;
+
+        /**
+         * Create a SatelliteController to act as a backend service of
+         * {@link SatelliteManager}
+         *
+         * @param context The Context for the SatelliteController.
+         */
+        protected TestSatelliteController(Context context, Looper looper) {
+            super(context, looper);
+            mProvisionStateChangedCallbacks = new HashMap<>();
+        }
+
+        @Override
+        public Boolean isSatelliteProvisioned() {
+            return mIsSatelliteProvisioned;
+        }
+
+        @Override
+        public boolean isSatelliteSupported() {
+            return true;
+        }
+
+        @Override
+        @SatelliteManager.SatelliteError public int registerForSatelliteProvisionStateChanged(
+                int subId, @NonNull ISatelliteProvisionStateCallback callback) {
+            mRegisterForSatelliteProvisionStateChangedCalls++;
+            Set<ISatelliteProvisionStateCallback> perSubscriptionCallbacks =
+                    mProvisionStateChangedCallbacks.getOrDefault(subId, new HashSet<>());
+            perSubscriptionCallbacks.add(callback);
+            mProvisionStateChangedCallbacks.put(subId, perSubscriptionCallbacks);
+            return SatelliteManager.SATELLITE_ERROR_NONE;
+        }
+
+        @Override
+        public void unregisterForSatelliteProvisionStateChanged(
+                int subId, @NonNull ISatelliteProvisionStateCallback callback) {
+            mUnregisterForSatelliteProvisionStateChangedCalls++;
+            Set<ISatelliteProvisionStateCallback> perSubscriptionCallbacks =
+                    mProvisionStateChangedCallbacks.get(subId);
+            if (perSubscriptionCallbacks != null) {
+                perSubscriptionCallbacks.remove(callback);
+            }
+        }
+
+        @Override
+        public void requestIsSatelliteCommunicationAllowedForCurrentLocation(int subId,
+                @NonNull ResultReceiver result) {
+            Bundle bundle = new Bundle();
+            bundle.putBoolean(SatelliteManager.KEY_SATELLITE_COMMUNICATION_ALLOWED,
+                    mIsSatelliteCommunicationAllowed);
+            result.send(SatelliteManager.SATELLITE_ERROR_NONE, bundle);
+        }
+
+        public void setIsSatelliteCommunicationAllowed(boolean allowed) {
+            mIsSatelliteCommunicationAllowed = allowed;
+        }
+
+        public int getRegisterForSatelliteProvisionStateChangedCalls() {
+            return mRegisterForSatelliteProvisionStateChangedCalls;
+        }
+
+        public int getUnregisterForSatelliteProvisionStateChangedCalls() {
+            return mUnregisterForSatelliteProvisionStateChangedCalls;
+        }
+
+        public void sendProvisionStateChangedEvent(int subId, boolean provisioned) {
+            mIsSatelliteProvisioned = provisioned;
+            Set<ISatelliteProvisionStateCallback> perSubscriptionCallbacks =
+                    mProvisionStateChangedCallbacks.get(subId);
+            if (perSubscriptionCallbacks != null) {
+                for (ISatelliteProvisionStateCallback callback : perSubscriptionCallbacks) {
+                    try {
+                        callback.onSatelliteProvisionStateChanged(provisioned);
+                    } catch (RemoteException ex) {
+                        Log.e(TAG, "sendProvisionStateChangedEvent: ex=" + ex);
+                    }
+                }
+            }
+        }
+    }
+
+    private static class TestImsManager extends ImsManager {
+
+        private final Set<RegistrationManager.RegistrationCallback> mCallbacks;
+        private int mAddRegistrationCallbackCalls = 0;
+        private int mRemoveRegistrationListenerCalls = 0;
+
+        /**
+         * Used for testing only to inject dependencies.
+         */
+        TestImsManager(Context context, int phoneId, MmTelFeatureConnectionFactory factory,
+                SubscriptionManagerProxy subManagerProxy, SettingsProxy settingsProxy,
+                BinderCacheManager binderCacheManager) {
+            super(context, phoneId, factory, subManagerProxy, settingsProxy, binderCacheManager);
+            mCallbacks = new HashSet<>();
+        }
+
+        @Override
+        public void addRegistrationCallback(RegistrationManager.RegistrationCallback callback,
+                Executor executor)
+                throws ImsException {
+            mAddRegistrationCallbackCalls++;
+
+            if (callback == null) {
+                throw new NullPointerException("registration callback can't be null");
+            }
+            if (executor == null) {
+                throw new NullPointerException("registration executor can't be null");
+            }
+
+            callback.setExecutor(executor);
+            mCallbacks.add(callback);
+        }
+
+        @Override
+        public void removeRegistrationListener(RegistrationManager.RegistrationCallback callback) {
+            mRemoveRegistrationListenerCalls++;
+
+            if (callback == null) {
+                throw new NullPointerException("registration callback can't be null");
+            }
+            mCallbacks.remove(callback);
+        }
+
+        public int getAddRegistrationCallbackCalls() {
+            return mAddRegistrationCallbackCalls;
+        }
+
+        public int getRemoveRegistrationListenerCalls() {
+            return mRemoveRegistrationListenerCalls;
+        }
+
+        public void sendImsRegistrationStateChangedEvent(boolean registered) {
+            if (registered) {
+                for (RegistrationManager.RegistrationCallback callback : mCallbacks) {
+                    callback.onRegistered(null);
+                }
+            } else {
+                for (RegistrationManager.RegistrationCallback callback : mCallbacks) {
+                    callback.onUnregistered(null);
+                }
+            }
+        }
+    }
+
+    private static class TestSOSMessageRecommender extends SatelliteSOSMessageRecommender {
+
+        /**
+         * Create an instance of SatelliteSOSMessageRecommender.
+         *
+         * @param looper              The looper used with the handler of this class.
+         * @param satelliteController The SatelliteController singleton instance.
+         * @param imsManager          The ImsManager instance associated with the phone, which is
+         *                            used for making the emergency call. This argument is not
+         *                            null only in unit tests.
+         * @param timeoutMillis       The timeout duration of the timer.
+         */
+        TestSOSMessageRecommender(Looper looper, SatelliteController satelliteController,
+                ImsManager imsManager, long timeoutMillis) {
+            super(looper, satelliteController, imsManager, timeoutMillis);
+        }
+
+        public boolean isTimerStarted() {
+            return hasMessages(EVENT_TIME_OUT);
+        }
+
+        public int getCountOfTimerStarted() {
+            return mCountOfTimerStarted;
+        }
+
+        public void sendServiceStateChangedEvent(@ServiceState.RegState int state) {
+            ServiceState serviceState = new ServiceState();
+            serviceState.setState(state);
+            sendMessage(obtainMessage(EVENT_CELLULAR_SERVICE_STATE_CHANGED,
+                    new AsyncResult(null, serviceState, null)));
+        }
+    }
+
+    private static class TestConnection extends Connection {
+        private final Set<String> mSentEvents;
+        TestConnection(String callId) {
+            setTelecomCallId(callId);
+            mSentEvents = new HashSet<>();
+        }
+
+        @Override
+        public void sendConnectionEvent(String event, Bundle extras) {
+            mSentEvents.add(event);
+        }
+
+        public boolean isEventSent(String event) {
+            return mSentEvents.contains(event);
+        }
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSessionControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSessionControllerTest.java
new file mode 100644
index 0000000..3ccf512
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSessionControllerTest.java
@@ -0,0 +1,489 @@
+/*
+ * 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.satellite;
+
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Looper;
+import android.os.Message;
+import android.telephony.satellite.ISatelliteStateCallback;
+import android.telephony.satellite.SatelliteManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Unit tests for SatelliteSessionController
+ */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class SatelliteSessionControllerTest extends TelephonyTest {
+    private static final String TAG = "SatelliteSessionControllerTest";
+    private static final long TEST_SATELLITE_STAY_AT_LISTENING_MILLIS = 200;
+    private static final long EVENT_PROCESSING_TIME_MILLIS = 100;
+
+    private static final String STATE_UNAVAILABLE = "UnavailableState";
+    private static final String STATE_POWER_OFF = "PowerOffState";
+    private static final String STATE_IDLE = "IdleState";
+    private static final String STATE_TRANSFERRING = "TransferringState";
+    private static final String STATE_LISTENING = "ListeningState";
+
+    private TestSatelliteModemInterface mSatelliteModemInterface;
+    private TestSatelliteSessionController mTestSatelliteSessionController;
+    private TestSatelliteStateCallback mTestSatelliteStateCallback;
+
+    @Mock
+    private SatelliteController mSatelliteController;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        MockitoAnnotations.initMocks(this);
+
+        mSatelliteModemInterface = new TestSatelliteModemInterface(
+                mContext, mSatelliteController, Looper.myLooper());
+        mTestSatelliteSessionController = new TestSatelliteSessionController(mContext,
+                Looper.myLooper(), true, mSatelliteModemInterface,
+                TEST_SATELLITE_STAY_AT_LISTENING_MILLIS,
+                TEST_SATELLITE_STAY_AT_LISTENING_MILLIS);
+        processAllMessages();
+
+        mTestSatelliteStateCallback = new TestSatelliteStateCallback();
+        mTestSatelliteSessionController.registerForSatelliteModemStateChanged(
+                mTestSatelliteStateCallback);
+        assertSuccessfulModemStateChangedCallback(
+                mTestSatelliteStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    public void testInitialState() {
+        /**
+         * Since satellite is not supported, SatelliteSessionController should move to UNAVAILABLE
+         * state.
+         */
+        TestSatelliteSessionController sessionController1 = new TestSatelliteSessionController(
+                mContext, Looper.myLooper(), false,
+                mSatelliteModemInterface, 100, 100);
+        assertNotNull(sessionController1);
+        processAllMessages();
+        assertEquals(STATE_UNAVAILABLE, sessionController1.getCurrentStateName());
+
+        /**
+         * Since satellite is supported, SatelliteSessionController should move to POWER_OFF state.
+         */
+        TestSatelliteSessionController sessionController2 = new TestSatelliteSessionController(
+                mContext, Looper.myLooper(), true,
+                mSatelliteModemInterface, 100, 100);
+        assertNotNull(sessionController2);
+        processAllMessages();
+        assertEquals(STATE_POWER_OFF, sessionController2.getCurrentStateName());
+    }
+
+    @Test
+    public void testUnavailableState() throws Exception {
+        /**
+         * Since satellite is not supported, SatelliteSessionController should move to UNAVAILABLE
+         * state.
+         */
+        TestSatelliteSessionController sessionController = new TestSatelliteSessionController(
+                mContext, Looper.myLooper(), false,
+                mSatelliteModemInterface, 100, 100);
+        assertNotNull(sessionController);
+        processAllMessages();
+        assertEquals(STATE_UNAVAILABLE, sessionController.getCurrentStateName());
+
+        /**
+         *  SatelliteSessionController should stay at UNAVAILABLE state even after it receives the
+         *  satellite radio powered-on state changed event.
+         */
+        sessionController.onSatelliteEnabledStateChanged(true);
+        processAllMessages();
+        assertEquals(STATE_UNAVAILABLE, sessionController.getCurrentStateName());
+    }
+
+    @Test
+    public void testStateTransition() {
+        /**
+         * Since satellite is supported, SatelliteSessionController should move to POWER_OFF state.
+         */
+        assertNotNull(mTestSatelliteSessionController);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+
+        // Power on the modem.
+        mTestSatelliteSessionController.onSatelliteEnabledStateChanged(true);
+        processAllMessages();
+
+        // SatelliteSessionController should move to IDLE state after the modem is powered on.
+        assertSuccessfulModemStateChangedCallback(
+                mTestSatelliteStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+        assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
+        assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
+
+        // Power off the modem.
+        mTestSatelliteSessionController.onSatelliteEnabledStateChanged(false);
+        processAllMessages();
+
+        // SatelliteSessionController should move back to POWER_OFF state.
+        assertSuccessfulModemStateChangedCallback(
+                mTestSatelliteStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+        assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
+
+        // Power on the modem.
+        mTestSatelliteSessionController.onSatelliteEnabledStateChanged(true);
+        processAllMessages();
+
+        // SatelliteSessionController should move to IDLE state after radio is turned on.
+        assertSuccessfulModemStateChangedCallback(
+                mTestSatelliteStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+        assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
+        assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
+
+        // Start sending datagrams
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+        processAllMessages();
+
+        // SatelliteSessionController should move to TRANSFERRING state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+        assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
+        assertTrue(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
+
+        // Sending datagrams failed
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+        processAllMessages();
+
+        // SatelliteSessionController should move to IDLE state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+        assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
+        assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
+
+        // Start sending datagrams again
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+        processAllMessages();
+
+        // SatelliteSessionController should move to TRANSFERRING state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+        assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
+        assertTrue(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
+
+        // Sending datagrams is successful and done.
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+        processAllMessages();
+
+        // SatelliteSessionController should move to LISTENING state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_LISTENING);
+        assertEquals(STATE_LISTENING, mTestSatelliteSessionController.getCurrentStateName());
+        assertEquals(1, mSatelliteModemInterface.getListeningEnabledCount());
+        assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
+
+        // Start receiving datagrams
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING);
+        processAllMessages();
+
+        // SatelliteSessionController should move to TRANSFERRING state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+        assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
+        assertEquals(1, mSatelliteModemInterface.getListeningDisabledCount());
+        assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
+
+        // Receiving datagrams is successful and done.
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+        processAllMessages();
+
+        // SatelliteSessionController should move to LISTENING state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_LISTENING);
+        assertEquals(STATE_LISTENING, mTestSatelliteSessionController.getCurrentStateName());
+        assertEquals(2, mSatelliteModemInterface.getListeningEnabledCount());
+        assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
+
+        // Start receiving datagrams again
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING);
+        processAllMessages();
+
+        // SatelliteSessionController should move to TRANSFERRING state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+        assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
+        assertEquals(2, mSatelliteModemInterface.getListeningDisabledCount());
+        assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
+
+        // Receiving datagrams failed.
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED);
+        processAllMessages();
+
+        // SatelliteSessionController should move to IDLE state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+        assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
+        assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
+
+        // Start receiving datagrams again
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING);
+        processAllMessages();
+
+        // SatelliteSessionController should move to TRANSFERRING state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+        assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
+        assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
+
+        // Receiving datagrams is successful and done.
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+        processAllMessages();
+
+        // SatelliteSessionController should move to LISTENING state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_LISTENING);
+        assertEquals(STATE_LISTENING, mTestSatelliteSessionController.getCurrentStateName());
+        assertEquals(3, mSatelliteModemInterface.getListeningEnabledCount());
+        assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
+
+        // Wait for timeout
+        moveTimeForward(TEST_SATELLITE_STAY_AT_LISTENING_MILLIS);
+        processAllMessages();
+
+        // SatelliteSessionController should move to IDLE state after timeout
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+        assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
+        assertEquals(3, mSatelliteModemInterface.getListeningDisabledCount());
+        assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
+
+        // Start receiving datagrams again
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING);
+        processAllMessages();
+
+        // SatelliteSessionController should move to TRANSFERRING state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+        assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
+        assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
+
+        // Start sending datagrams
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING);
+        processAllMessages();
+
+        // SatelliteSessionController should stay at TRANSFERRING state.
+        assertModemStateChangedCallbackNotCalled(mTestSatelliteStateCallback);
+        assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
+        assertTrue(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
+
+        // Receiving datagrams failed.
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED);
+        processAllMessages();
+
+        // SatelliteSessionController should stay at TRANSFERRING state instead of moving to IDLE
+        // state.
+        assertModemStateChangedCallbackNotCalled(mTestSatelliteStateCallback);
+        assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
+        assertTrue(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
+
+        // Start receiving datagrams again.
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING);
+        processAllMessages();
+
+        // SatelliteSessionController should stay at TRANSFERRING state.
+        assertModemStateChangedCallbackNotCalled(mTestSatelliteStateCallback);
+        assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
+        assertTrue(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
+
+        // Sending datagrams failed.
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING);
+        processAllMessages();
+
+        // SatelliteSessionController should stay at TRANSFERRING state instead of moving to IDLE
+        // state.
+        assertModemStateChangedCallbackNotCalled(mTestSatelliteStateCallback);
+        assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
+        assertTrue(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
+
+        // Power off the modem.
+        mTestSatelliteSessionController.onSatelliteEnabledStateChanged(false);
+        processAllMessages();
+
+        // SatelliteSessionController should move to POWER_OFF state.
+        assertSuccessfulModemStateChangedCallback(
+                mTestSatelliteStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+        assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
+    }
+
+    private static class TestSatelliteModemInterface extends SatelliteModemInterface {
+        private final AtomicInteger mListeningEnabledCount = new AtomicInteger(0);
+        private final AtomicInteger mListeningDisabledCount = new AtomicInteger(0);
+
+        TestSatelliteModemInterface(@NonNull Context context,
+                SatelliteController satelliteController, @NonNull Looper looper) {
+            super(context, satelliteController, looper);
+            mExponentialBackoff.stop();
+        }
+
+        @Override
+        protected void bindService() {
+            logd("TestSatelliteModemInterface: bindService");
+        }
+
+        @Override
+        protected void unbindService() {
+            logd("TestSatelliteModemInterface: unbindService");
+        }
+
+        @Override
+        public void requestSatelliteListeningEnabled(boolean enable, int timeout,
+                @Nullable Message message) {
+            if (enable) mListeningEnabledCount.incrementAndGet();
+            else mListeningDisabledCount.incrementAndGet();
+        }
+
+        public int getListeningEnabledCount() {
+            return mListeningEnabledCount.get();
+        }
+
+        public int getListeningDisabledCount() {
+            return mListeningDisabledCount.get();
+        }
+    }
+
+    private static class TestSatelliteSessionController extends SatelliteSessionController {
+        TestSatelliteSessionController(Context context, Looper looper, boolean isSatelliteSupported,
+                SatelliteModemInterface satelliteModemInterface,
+                long satelliteStayAtListeningFromSendingMillis,
+                long satelliteStayAtListeningFromReceivingMillis) {
+            super(context, looper, isSatelliteSupported, satelliteModemInterface,
+                    satelliteStayAtListeningFromSendingMillis,
+                    satelliteStayAtListeningFromReceivingMillis);
+        }
+
+        public String getCurrentStateName() {
+            return getCurrentState().getName();
+        }
+
+        public boolean isSendingTriggeredDuringTransferringState() {
+            return mIsSendingTriggeredDuringTransferringState.get();
+        }
+    }
+
+    private static class TestSatelliteStateCallback extends ISatelliteStateCallback.Stub {
+        private final AtomicInteger mModemState = new AtomicInteger(
+                SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+        private final Semaphore mSemaphore = new Semaphore(0);
+
+        @Override
+        public void onSatelliteModemStateChanged(int state) {
+            logd("onSatelliteModemStateChanged: state=" + state);
+            mModemState.set(state);
+            try {
+                mSemaphore.release();
+            } catch (Exception ex) {
+                logd("onSatelliteModemStateChanged: Got exception, ex=" + ex);
+            }
+        }
+
+        public boolean waitUntilResult() {
+            try {
+                if (!mSemaphore.tryAcquire(EVENT_PROCESSING_TIME_MILLIS, TimeUnit.MILLISECONDS)) {
+                    logd("Timeout to receive onSatelliteModemStateChanged");
+                    return false;
+                }
+                return true;
+            } catch (Exception ex) {
+                logd("onSatelliteModemStateChanged: Got exception=" + ex);
+                return false;
+            }
+        }
+
+        public int getModemState() {
+            return mModemState.get();
+        }
+    }
+
+    private static void assertSuccessfulModemStateChangedCallback(
+            TestSatelliteStateCallback callback,
+            @SatelliteManager.SatelliteModemState int expectedModemState) {
+        boolean successful = callback.waitUntilResult();
+        assertTrue(successful);
+        assertEquals(expectedModemState, callback.getModemState());
+    }
+
+    private static void assertModemStateChangedCallbackNotCalled(
+            TestSatelliteStateCallback callback) {
+        boolean successful = callback.waitUntilResult();
+        assertFalse(successful);
+    }
+}
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 9e418d5..9898353 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java
@@ -1226,6 +1226,8 @@
         mDatabaseManagerUT.setSubscriptionProperty(1, SimInfo.COLUMN_ENHANCED_4G_MODE_ENABLED, 1);
         assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(1).getEnhanced4GModeEnabled())
                 .isEqualTo(1);
+        assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(1).isEnhanced4GModeEnabled())
+                .isTrue();
     }
 
     @Test
@@ -1247,6 +1249,8 @@
         mDatabaseManagerUT.setSubscriptionProperty(1, SimInfo.COLUMN_VT_IMS_ENABLED, 1);
         assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(1).getVideoTelephonyEnabled())
                 .isEqualTo(1);
+        assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(1).isVideoTelephonyEnabled())
+                .isTrue();
     }
 
     @Test
@@ -1363,6 +1367,8 @@
         mDatabaseManagerUT.setSubscriptionProperty(1, SimInfo.COLUMN_VOIMS_OPT_IN_STATUS, 1);
         assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(1)
                 .getVoImsOptInEnabled()).isEqualTo(1);
+        assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(1).isVoImsOptInEnabled())
+                .isTrue();
     }
 
 
@@ -1613,6 +1619,8 @@
         mDatabaseManagerUT.setSubscriptionProperty(1, SimInfo.COLUMN_IMS_RCS_UCE_ENABLED, 1);
         assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(1).getRcsUceEnabled())
                 .isEqualTo(1);
+        assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(1).isRcsUceEnabled())
+                .isTrue();
     }
 
     @Test
@@ -1635,6 +1643,8 @@
         mDatabaseManagerUT.setSubscriptionProperty(1, SimInfo.COLUMN_CROSS_SIM_CALLING_ENABLED, 1);
         assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(1).getCrossSimCallingEnabled())
                 .isEqualTo(1);
+        assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(1).isCrossSimCallingEnabled())
+                .isTrue();
     }
 
     @Test
@@ -1736,6 +1746,8 @@
                 1, SimInfo.COLUMN_NR_ADVANCED_CALLING_ENABLED, 1);
         assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(1)
                 .getNrAdvancedCallingEnabled()).isEqualTo(1);
+        assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(1)
+                .isNrAdvancedCallingEnabled()).isTrue();
     }
 
     @Test
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 d61a57f..e03256b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionInfoInternalTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionInfoInternalTest.java
@@ -218,6 +218,7 @@
     public void testEquals() {
         SubscriptionInfoInternal another = new SubscriptionInfoInternal.Builder(mSubInfo).build();
         assertThat(another).isEqualTo(mSubInfo);
+        assertThat(another.hashCode()).isEqualTo(mSubInfo.hashCode());
     }
 
     @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 22e1067..cae9626 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java
@@ -60,7 +60,6 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -72,7 +71,6 @@
 import android.compat.testing.PlatformCompatChangeRule;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.content.res.Resources;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -161,7 +159,6 @@
     public void setUp() throws Exception {
         logd("SubscriptionManagerServiceTest +Setup!");
         super.setUp(getClass().getSimpleName());
-        enableSubscriptionManagerService(true);
 
         // Dual-SIM configuration
         mPhones = new Phone[] {mPhone, mPhone2};
@@ -195,6 +192,7 @@
 
         ((MockContentResolver) mContext.getContentResolver()).addProvider(
                 Telephony.Carriers.CONTENT_URI.getAuthority(), mSubscriptionProvider);
+
         mSubscriptionManagerServiceUT = new SubscriptionManagerService(mContext, Looper.myLooper());
 
         monitorTestableLooper(new TestableLooper(getBackgroundHandler().getLooper()));
@@ -302,13 +300,6 @@
         return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     }
 
-    private void enableGetSubscriptionUserHandle() {
-        Resources mResources = mock(Resources.class);
-        doReturn(true).when(mResources).getBoolean(
-                eq(com.android.internal.R.bool.config_enable_get_subscription_user_handle));
-        doReturn(mResources).when(mContext).getResources();
-    }
-
     @Test
     public void testBroadcastOnInitialization() {
         ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
@@ -1057,7 +1048,6 @@
     @Test
     public void testSetGetSubscriptionUserHandle() {
         insertSubscription(FAKE_SUBSCRIPTION_INFO1);
-        enableGetSubscriptionUserHandle();
 
         // Should fail without MANAGE_SUBSCRIPTION_USER_ASSOCIATION
         assertThrows(SecurityException.class, () -> mSubscriptionManagerServiceUT
@@ -1091,7 +1081,6 @@
     @Test
     public void testIsSubscriptionAssociatedWithUser() {
         insertSubscription(FAKE_SUBSCRIPTION_INFO1);
-        enableGetSubscriptionUserHandle();
 
         // Should fail without MANAGE_SUBSCRIPTION_USER_ASSOCIATION
         assertThrows(SecurityException.class, () -> mSubscriptionManagerServiceUT
@@ -1810,9 +1799,9 @@
 
         mContextFixture.addCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE);
         assertThat(mSubscriptionManagerServiceUT.removeSubInfo(FAKE_ICCID1,
-                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM)).isEqualTo(0);
+                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM)).isEqualTo(true);
         assertThat(mSubscriptionManagerServiceUT.removeSubInfo(FAKE_ICCID2,
-                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM)).isEqualTo(0);
+                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM)).isEqualTo(true);
 
         mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
         assertThat(mSubscriptionManagerServiceUT.getAllSubInfoList(
@@ -2157,8 +2146,13 @@
         insertSubscription(FAKE_SUBSCRIPTION_INFO1);
         insertSubscription(FAKE_SUBSCRIPTION_INFO2);
 
-        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
         final StringWriter stringWriter = new StringWriter();
+        assertThrows(SecurityException.class, ()
+                -> mSubscriptionManagerServiceUT.dump(new FileDescriptor(),
+                new PrintWriter(stringWriter), null));
+
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.DUMP);
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
         mSubscriptionManagerServiceUT.dump(new FileDescriptor(), new PrintWriter(stringWriter),
                 null);
         assertThat(stringWriter.toString().length()).isGreaterThan(0);
@@ -2248,7 +2242,7 @@
                 SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM);
 
         assertThat(mSubscriptionManagerServiceUT.removeSubInfo(FAKE_MAC_ADDRESS1,
-                SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM)).isEqualTo(0);
+                SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM)).isEqualTo(true);
         assertThat(mSubscriptionManagerServiceUT.getAllSubInfoList(
                 CALLING_PACKAGE, CALLING_FEATURE)).isEmpty();
         assertThat(mSubscriptionManagerServiceUT.getActiveSubIdList(false)).isEmpty();
@@ -2363,6 +2357,19 @@
     }
 
     @Test
+    public void testSimNotReadyBySimDeactivate() {
+        insertSubscription(FAKE_SUBSCRIPTION_INFO1);
+
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+        mSubscriptionManagerServiceUT.updateSimState(
+                0, TelephonyManager.SIM_STATE_NOT_READY, null, null);
+        doReturn(true).when(mUiccProfile).isEmptyProfile();
+        processAllMessages();
+
+        assertThat(mSubscriptionManagerServiceUT.getActiveSubIdList(false)).isEmpty();
+    }
+
+    @Test
     public void testInactiveSimRemoval() {
         insertSubscription(FAKE_SUBSCRIPTION_INFO2);
 
@@ -2438,4 +2445,62 @@
         assertThat(mSubscriptionManagerServiceUT.getSubscriptionInfo(2).isEmbedded())
                 .isEqualTo(true);
     }
+
+
+    @Test
+    public void testNonNullSubInfoBuilderFromEmbeddedProfile() {
+        EuiccProfileInfo profileInfo1 = new EuiccProfileInfo.Builder(FAKE_ICCID1)
+                .setIccid(FAKE_ICCID1) //can't build profile with null iccid.
+                .setNickname(null) //nullable
+                .setServiceProviderName(null) //nullable
+                .setProfileName(null) //nullable
+                .setCarrierIdentifier(null) //nullable
+                .setUiccAccessRule(null) //nullable
+                .build();
+
+        EuiccProfileInfo profileInfo2 = new EuiccProfileInfo.Builder(FAKE_ICCID2)
+                .setIccid(FAKE_ICCID2) //impossible to build profile with null iccid.
+                .setNickname(null) //nullable
+                .setCarrierIdentifier(new CarrierIdentifier(FAKE_MCC2, FAKE_MNC2, null, null, null,
+                        null, FAKE_CARRIER_ID2, FAKE_CARRIER_ID2)) //not allow null mcc/mnc.
+                .setUiccAccessRule(null) //nullable
+                .build();
+
+        GetEuiccProfileInfoListResult result = new GetEuiccProfileInfoListResult(
+                EuiccService.RESULT_OK, new EuiccProfileInfo[]{profileInfo1}, false);
+        doReturn(result).when(mEuiccController).blockingGetEuiccProfileInfoList(eq(1));
+        result = new GetEuiccProfileInfoListResult(EuiccService.RESULT_OK,
+                new EuiccProfileInfo[]{profileInfo2}, false);
+        doReturn(result).when(mEuiccController).blockingGetEuiccProfileInfoList(eq(2));
+        doReturn(TelephonyManager.INVALID_PORT_INDEX).when(mUiccSlot)
+                .getPortIndexFromIccId(anyString());
+
+        mSubscriptionManagerServiceUT.updateEmbeddedSubscriptions(List.of(1, 2), null);
+        processAllMessages();
+
+        SubscriptionInfoInternal subInfo = mSubscriptionManagerServiceUT
+                .getSubscriptionInfoInternal(1);
+        assertThat(subInfo.getSubscriptionId()).isEqualTo(1);
+        assertThat(subInfo.getIccId()).isEqualTo(FAKE_ICCID1);
+        assertThat(subInfo.getDisplayName()).isEqualTo("");
+        assertThat(subInfo.getDisplayNameSource()).isEqualTo(
+                SubscriptionManager.NAME_SOURCE_UNKNOWN);
+        assertThat(subInfo.getMcc()).isEqualTo("");
+        assertThat(subInfo.getMnc()).isEqualTo("");
+        assertThat(subInfo.isEmbedded()).isTrue();
+        assertThat(subInfo.isRemovableEmbedded()).isFalse();
+        assertThat(subInfo.getNativeAccessRules()).isEqualTo(new byte[]{});
+
+        subInfo = mSubscriptionManagerServiceUT.getSubscriptionInfoInternal(2);
+        assertThat(subInfo.getSubscriptionId()).isEqualTo(2);
+        assertThat(subInfo.getIccId()).isEqualTo(FAKE_ICCID2);
+        assertThat(subInfo.getDisplayName()).isEqualTo("");
+        assertThat(subInfo.getDisplayNameSource()).isEqualTo(
+                SubscriptionManager.NAME_SOURCE_UNKNOWN);
+        assertThat(subInfo.getMcc()).isEqualTo(FAKE_MCC2);
+        assertThat(subInfo.getMnc()).isEqualTo(FAKE_MNC2);
+        assertThat(subInfo.isEmbedded()).isTrue();
+        assertThat(subInfo.isRemovableEmbedded()).isFalse();
+        assertThat(subInfo.getNativeAccessRules()).isEqualTo(new byte[]{});
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/AdnRecordCacheTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/AdnRecordCacheTest.java
new file mode 100644
index 0000000..c040b9e
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/AdnRecordCacheTest.java
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) 2022 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.uicc;
+
+import static com.android.internal.telephony.uicc.IccConstants.EF_ADN;
+import static com.android.internal.telephony.uicc.IccConstants.EF_MBDN;
+import static com.android.internal.telephony.uicc.IccConstants.EF_PBR;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.test.TestLooper;
+
+import com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.gsm.UsimPhoneBookManager;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+
+public class AdnRecordCacheTest extends TelephonyTest {
+
+    private AdnRecordCacheUT mAdnRecordCache;
+    private TestLooper mTestLooper;
+    private Handler mTestHandler;
+    private IccFileHandler mFhMock;
+    private UsimPhoneBookManager mUsimPhoneBookManager;
+
+    @SuppressWarnings("ClassCanBeStatic")
+    private class AdnRecordCacheUT extends AdnRecordCache {
+        AdnRecordCacheUT(IccFileHandler fh, UsimPhoneBookManager usimPhoneBookManager) {
+            super(fh, usimPhoneBookManager);
+        }
+
+        protected void setAdnLikeWriters(int key, ArrayList<Message> waiters) {
+            super.setAdnLikeWriters(key, waiters);
+        }
+
+        protected void setAdnLikeFiles(int key, ArrayList<AdnRecord> adnRecordList) {
+            super.setAdnLikeFiles(key, adnRecordList);
+        }
+
+        protected void setUserWriteResponse(int key, Message message) {
+            super.setUserWriteResponse(key, message);
+        }
+
+        protected UsimPhoneBookManager getUsimPhoneBookManager() {
+            return super.getUsimPhoneBookManager();
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        // Mocked classes
+        mFhMock = mock(IccFileHandler.class);
+        mUsimPhoneBookManager = mock(UsimPhoneBookManager.class);
+        mTestLooper = new TestLooper();
+        mTestHandler = new Handler(mTestLooper.getLooper());
+        mTestHandler.post(
+                () -> mAdnRecordCache = new AdnRecordCacheUT(mFhMock, mUsimPhoneBookManager));
+        mTestLooper.dispatchAll();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mTestLooper != null) {
+            mTestLooper.dispatchAll();
+            mTestLooper = null;
+        }
+        mTestHandler.removeCallbacksAndMessages(null);
+        mTestHandler = null;
+        mAdnRecordCache = null;
+        super.tearDown();
+    }
+
+    @Test
+    public void resetTest() {
+        Message message1 = Message.obtain(mTestHandler);
+        Message message2 = Message.obtain(mTestHandler);
+
+        // test data to create mAdnLikeWaiters
+        ArrayList<Message> waiters = new ArrayList<>();
+        waiters.add(message1);
+        mAdnRecordCache.setAdnLikeWriters(EF_MBDN, waiters);
+
+        // test data to create mAdnLikeFiles
+        setAdnLikeFiles(EF_MBDN);
+
+        // test data to create mUserWriteResponse
+        mAdnRecordCache.setUserWriteResponse(EF_MBDN, message2);
+
+        mAdnRecordCache.reset();
+
+        mTestLooper.dispatchAll();
+        AsyncResult ar1 = (AsyncResult) message1.obj;
+        AsyncResult ar2 = (AsyncResult) message2.obj;
+        Assert.assertTrue(ar1.exception.toString().contains("AdnCache reset"));
+        Assert.assertTrue(ar2.exception.toString().contains("AdnCace reset"));
+    }
+
+    @Test
+    public void updateAdnByIndexEfException() {
+        int efId = 0x6FC5;
+        Message message = Message.obtain(mTestHandler);
+        mAdnRecordCache.updateAdnByIndex(efId, null, 0, null, message);
+        mTestLooper.dispatchAll();
+
+        AsyncResult ar = (AsyncResult) message.obj;
+        Assert.assertNotNull(ar.exception);
+        assertTrue((ar.exception.toString().contains("EF is not known ADN-like EF:0x6FC5")));
+    }
+
+    @Test
+    public void updateAdnByIndex_WriteResponseException() {
+        int efId = EF_MBDN;
+        Message message = Message.obtain(mTestHandler);
+        Message message2 = Message.obtain(mTestHandler);
+        AdnRecord adnRecord = new AdnRecord("AlphaTag", "123456789");
+        // test data to create mUserWriteResponse
+        mAdnRecordCache.setUserWriteResponse(efId, message2);
+        mAdnRecordCache.updateAdnByIndex(efId, adnRecord, 0, null, message);
+
+        AsyncResult ar = (AsyncResult) message.obj;
+        Assert.assertNotNull(ar.exception);
+        assertTrue((ar.exception.toString().contains("Have pending update for EF:0x6FC7")));
+    }
+
+    @Test
+    public void updateAdnByIndex() {
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(2);
+                    AsyncResult.forMessage(response, "success2", null);
+                    response.sendToTarget();
+                    return response;
+                })
+                .when(mFhMock)
+                .getEFLinearRecordSize(anyInt(), isNull(), any(Message.class));
+
+        Message message = Message.obtain(mTestHandler);
+        Assert.assertNotNull(message);
+        AdnRecord adnRecord = new AdnRecord("AlphaTag", "123456789");
+        // test data to create mUserWriteResponse
+        mAdnRecordCache.updateAdnByIndex(EF_MBDN, adnRecord, 0, null, message);
+        mTestLooper.startAutoDispatch();
+        verify(mFhMock, times(1)).getEFLinearRecordSize(anyInt(), isNull(), any(Message.class));
+    }
+
+    @Test
+    public void updateAdnBySearch_EfException() {
+        int efId = 0x6FC5;
+        Message message = Message.obtain(mTestHandler);
+        mAdnRecordCache.updateAdnBySearch(efId, null, null, null, message);
+        mTestLooper.dispatchAll();
+
+        AsyncResult ar = (AsyncResult) message.obj;
+        Assert.assertNotNull(ar.exception);
+        assertTrue((ar.exception.toString().contains("EF is not known ADN-like EF:0x6FC5")));
+    }
+
+    @Test
+    public void updateAdnBySearch_Exception() {
+        Message message = Message.obtain(mTestHandler);
+        mAdnRecordCache.updateAdnBySearch(EF_MBDN, null, null, null, message);
+        mTestLooper.dispatchAll();
+
+        AsyncResult ar = (AsyncResult) message.obj;
+        Assert.assertNotNull(ar.exception);
+        assertTrue((ar.exception.toString().contains("Adn list not exist for EF:0x6FC7")));
+    }
+
+    @Test
+    public void updateAdnBySearch_AdnListError() {
+        int efId = EF_MBDN;
+        setAdnLikeFiles(efId);
+        Message message = Message.obtain(mTestHandler);
+        AdnRecord oldAdn = new AdnRecord("oldAlphaTag", "123456789");
+        mAdnRecordCache.updateAdnBySearch(efId, oldAdn, null, null, message);
+        mTestLooper.dispatchAll();
+
+        AsyncResult ar = (AsyncResult) message.obj;
+        Assert.assertNotNull(ar.exception);
+        assertTrue((ar.exception.toString().contains(
+                "Adn record don't exist for ADN Record 'oldAlphaTag'")));
+    }
+
+    @Test
+    public void updateAdnBySearch_PendingUpdate() {
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(2);
+                    AsyncResult.forMessage(response, "success2", null);
+                    response.sendToTarget();
+                    return response;
+                })
+                .when(mFhMock)
+                .getEFLinearRecordSize(anyInt(), isNull(), any(Message.class));
+
+        int efId = EF_MBDN;
+        setAdnLikeFiles(efId);
+        Message message = Message.obtain(mTestHandler);
+        AdnRecord oldAdn = new AdnRecord("AlphaTag", "123456789");
+        mAdnRecordCache.updateAdnBySearch(efId, oldAdn, null, null, message);
+        mTestLooper.dispatchAll();
+
+        verify(mFhMock, times(1)).getEFLinearRecordSize(anyInt(), isNull(), any(Message.class));
+    }
+
+    @Test
+    public void updateAdnBySearch() {
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(2);
+                    AsyncResult.forMessage(response, "success", null);
+                    response.sendToTarget();
+                    return response;
+                })
+                .when(mFhMock)
+                .getEFLinearRecordSize(anyInt(), isNull(), any(Message.class));
+
+        int efId = EF_MBDN;
+        setAdnLikeFiles(efId);
+        Message message = Message.obtain(mTestHandler);
+        AdnRecord oldAdn = new AdnRecord("AlphaTag", "123456789");
+        mAdnRecordCache.updateAdnBySearch(efId, oldAdn, null, null, message);
+        mTestLooper.dispatchAll();
+
+        verify(mFhMock, times(1)).getEFLinearRecordSize(anyInt(), isNull(), any(Message.class));
+    }
+
+
+    @Test
+    public void updateAdnBySearch_AdnException() {
+        doReturn(null).when(mUsimPhoneBookManager).loadEfFilesFromUsim();
+        Message message = Message.obtain(mTestHandler);
+        AdnRecord oldAdn = new AdnRecord("oldAlphaTag", "123456789");
+        mAdnRecordCache.updateAdnBySearch(EF_PBR, oldAdn, null, null, message);
+        mTestLooper.dispatchAll();
+
+        AsyncResult ar = (AsyncResult) message.obj;
+        Assert.assertNotNull(ar.exception);
+        assertTrue((ar.exception.toString().contains("Adn list not exist for EF:0x4F30")));
+    }
+
+    @Test
+    public void requestLoadAllAdnLike_AlreadyLoadedEf() {
+        int efId = EF_MBDN;
+        setAdnLikeFiles(efId);
+        Message message = Message.obtain(mTestHandler);
+        mAdnRecordCache.requestLoadAllAdnLike(efId, 0, message);
+        mTestLooper.dispatchAll();
+
+        AsyncResult ar = (AsyncResult) message.obj;
+        Assert.assertNull(ar.exception);
+        Assert.assertNotNull(ar.result);
+    }
+
+    @Test
+    public void requestLoadAllAdnLike_AlreadyLoadingEf() {
+        int efId = EF_MBDN;
+        // test data to create mAdnLikeWaiters
+        Message message = Message.obtain(mTestHandler);
+        ArrayList<Message> waiters = new ArrayList<>();
+        waiters.add(message);
+        mAdnRecordCache.setAdnLikeWriters(efId, waiters);
+        mAdnRecordCache.requestLoadAllAdnLike(efId, 0, message);
+        mTestLooper.dispatchAll();
+
+        AsyncResult ar = (AsyncResult) message.obj;
+        Assert.assertNull(ar);
+    }
+
+    @Test
+    public void requestLoadAllAdnLike_NotKnownEf() {
+        Message message = Message.obtain(mTestHandler);
+        mAdnRecordCache.requestLoadAllAdnLike(EF_MBDN, -1, message);
+        mTestLooper.dispatchAll();
+
+        AsyncResult ar = (AsyncResult) message.obj;
+        Assert.assertTrue(ar.exception.toString().contains("EF is not known ADN-like EF:0x"));
+    }
+
+    @Test
+    public void requestLoadAllAdnLike() {
+                doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(2);
+                    AsyncResult.forMessage(response, null, new CommandException(
+                            CommandException.Error.REQUEST_NOT_SUPPORTED));
+                    response.sendToTarget();
+                    return response;
+                })
+                .when(mFhMock)
+                .loadEFLinearFixedAll(anyInt(), anyString(), any(Message.class));
+
+        Message message = Message.obtain(mTestHandler);
+        mAdnRecordCache.requestLoadAllAdnLike(EF_ADN, 0x6FC8, message);
+        mTestLooper.dispatchAll();
+
+        verify(mFhMock, times(1)).loadEFLinearFixedAll(anyInt(), anyString(), any(Message.class));
+    }
+
+    private void setAdnLikeFiles(int ef) {
+        // test data to create mAdnLikeFiles
+        ArrayList<AdnRecord> adnRecordList = new ArrayList<>();
+        AdnRecord adnRecord = new AdnRecord("AlphaTag", "123456789");
+        adnRecordList.add(adnRecord);
+        mAdnRecordCache.setAdnLikeFiles(ef, adnRecordList);
+    }
+}
\ No newline at end of file
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/IccFileHandlerTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/IccFileHandlerTest.java
new file mode 100644
index 0000000..63e68e2
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/IccFileHandlerTest.java
@@ -0,0 +1,537 @@
+/*
+ * Copyright (C) 2022 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.uicc;
+
+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.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.test.TestLooper;
+import android.util.Log;
+
+import com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.CommandsInterface;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+
+public class IccFileHandlerTest {
+    CommandsInterface mCi;
+    IccFileHandler mIccFileHandler;
+    private TestLooper mTestLooper;
+    private Handler mTestHandler;
+
+    @Before
+    public void setUp() throws Exception {
+        mCi = mock(CommandsInterface.class);
+        mTestLooper = new TestLooper();
+        mTestHandler = new Handler(mTestLooper.getLooper());
+        mTestHandler.post(
+                () -> mIccFileHandler = new IccFileHandler(mCi) {
+                    @Override
+                    protected String getEFPath(int efid) {
+                        switch (efid) {
+                            case 0x4f30:
+                            case 0x4f3a:
+                                return "3F007F105F3A";
+                        }
+                        return "";
+                    }
+
+                    @Override
+                    protected void logd(String s) {
+                        Log.d("IccFileHandlerTest", s);
+                    }
+
+                    @Override
+                    protected void loge(String s) {
+                        Log.d("IccFileHandlerTest", s);
+                    }
+                });
+
+
+        mTestLooper.dispatchAll();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mTestLooper != null) {
+            mTestLooper.dispatchAll();
+            mTestLooper = null;
+        }
+        mTestHandler.removeCallbacksAndMessages(null);
+        mTestHandler = null;
+        mIccFileHandler = null;
+        mCi = null;
+    }
+
+    @Test
+    public void loadEFLinearFixed_WithNullPath() {
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(9);
+                    AsyncResult.forMessage(response, "Success", null);
+                    response.sendToTarget();
+                    return null;
+                })
+                .when(mCi)
+                .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(),
+                        isNull(), isNull(), isNull(), any(Message.class));
+
+        Message message = Message.obtain(mTestHandler);
+        mIccFileHandler.loadEFLinearFixed(0, "", 0, message);
+        verify(mCi, times(1)).iccIOForApp(anyInt(), anyInt(), anyString(),
+                anyInt(), anyInt(), anyInt(), isNull(), isNull(), isNull(), any(Message.class));
+    }
+
+    @Test
+    public void loadEFLinearFixed() {
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(9);
+                    AsyncResult.forMessage(response, "Success", null);
+                    response.sendToTarget();
+                    return null;
+                })
+                .when(mCi)
+                .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(),
+                        isNull(), isNull(), isNull(), any(Message.class));
+
+        Message message = Message.obtain(mTestHandler);
+        mIccFileHandler.loadEFLinearFixed(0, 0, message);
+        verify(mCi, times(1)).iccIOForApp(anyInt(), anyInt(), anyString(),
+                anyInt(), anyInt(), anyInt(), isNull(), isNull(), isNull(), any(Message.class));
+    }
+
+    @Test
+    public void loadEFImgLinearFixed() {
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(9);
+                    AsyncResult.forMessage(response, "Success", null);
+                    response.sendToTarget();
+                    return null;
+                })
+                .when(mCi)
+                .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(),
+                        isNull(), isNull(), isNull(), any(Message.class));
+
+        Message message = Message.obtain(mTestHandler);
+        mIccFileHandler.loadEFImgLinearFixed(0, message);
+        verify(mCi, times(1)).iccIOForApp(anyInt(), anyInt(), anyString(),
+                anyInt(), anyInt(), anyInt(), isNull(), isNull(), isNull(), any(Message.class));
+    }
+
+    @Test
+    public void getEFLinearRecordSize_WithNullPath() {
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(9);
+                    AsyncResult.forMessage(response, "Success", null);
+                    response.sendToTarget();
+                    return null;
+                })
+                .when(mCi)
+                .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(),
+                        isNull(), isNull(), isNull(), any(Message.class));
+
+        Message message = Message.obtain(mTestHandler);
+        mIccFileHandler.getEFLinearRecordSize(0, "", message);
+        verify(mCi, times(1)).iccIOForApp(anyInt(), anyInt(), anyString(),
+                anyInt(), anyInt(), anyInt(), isNull(), isNull(), isNull(), any(Message.class));
+    }
+
+    @Test
+    public void getEFLinearRecordSize() {
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(9);
+                    AsyncResult.forMessage(response, "Success", null);
+                    response.sendToTarget();
+                    return null;
+                })
+                .when(mCi)
+                .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(),
+                        isNull(), isNull(), isNull(), any(Message.class));
+
+        Message message = Message.obtain(mTestHandler);
+        mIccFileHandler.getEFLinearRecordSize(0, message);
+        verify(mCi, times(1)).iccIOForApp(anyInt(), anyInt(), anyString(),
+                anyInt(), anyInt(), anyInt(), isNull(), isNull(), isNull(), any(Message.class));
+    }
+
+    @Test
+    public void getEFTransparentRecordSize() {
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(9);
+                    AsyncResult.forMessage(response, "Success", null);
+                    response.sendToTarget();
+                    return null;
+                })
+                .when(mCi)
+                .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(),
+                        isNull(), isNull(), isNull(), any(Message.class));
+
+        Message message = Message.obtain(mTestHandler);
+        mIccFileHandler.getEFTransparentRecordSize(0, message);
+        verify(mCi, times(1)).iccIOForApp(anyInt(), anyInt(), anyString(),
+                anyInt(), anyInt(), anyInt(), isNull(), isNull(), isNull(), any(Message.class));
+    }
+
+    @Test
+    public void loadEFLinearFixedAll_FileNotFoundAtGetRecord() throws InterruptedException {
+        int efId = 0x4f30;
+        final CountDownLatch latch = new CountDownLatch(1);
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(9);
+                    IccIoResult iir = new IccIoResult(0x94, 0x00,
+                            IccUtils.hexStringToBytes(null));
+                    AsyncResult.forMessage(response, iir, null);
+                    mTestHandler.postDelayed(latch::countDown, 100);
+                    response.sendToTarget();
+                    return null;
+                }).when(mCi).iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(),
+                anyInt(), isNull(), isNull(), isNull(), any(Message.class));
+
+        Message message = Message.obtain(mTestHandler);
+        mIccFileHandler.loadEFLinearFixedAll(efId, null, message);
+        mTestLooper.startAutoDispatch();
+        latch.await(5, java.util.concurrent.TimeUnit.SECONDS);
+        AsyncResult ar = (AsyncResult) message.obj;
+        assertNotNull(ar);
+        assertTrue(ar.exception instanceof IccFileNotFound);
+    }
+
+    @Test
+    public void loadEFLinearFixedAll_FileNotFoundAtReadRecord() throws InterruptedException {
+        int efid = 0x4f30;
+        final CountDownLatch latch = new CountDownLatch(2);
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(9);
+                    IccIoResult iir = null;
+                    if (response.what == 6) {
+                        iir = new IccIoResult(0x90, 0x00, IccUtils.hexStringToBytes(
+                                "000000454F30040000FFFF01020145"));
+                        latch.countDown();
+                    } else if (response.what == 7) {
+                        iir = new IccIoResult(0x94, 0x00, IccUtils.hexStringToBytes(null));
+                        mTestHandler.postDelayed(latch::countDown, 100);
+                    }
+                    AsyncResult.forMessage(response, iir, null);
+                    response.sendToTarget();
+                    return null;
+                }).when(mCi).iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(),
+                anyInt(), isNull(), isNull(), isNull(), any(Message.class));
+
+        Message message = Message.obtain(mTestHandler);
+        mIccFileHandler.loadEFLinearFixedAll(efid, null, message);
+        mTestLooper.startAutoDispatch();
+        latch.await(5, java.util.concurrent.TimeUnit.SECONDS);
+        AsyncResult ar = (AsyncResult) message.obj;
+        assertNotNull(ar);
+        assertTrue(ar.exception instanceof IccFileNotFound);
+    }
+
+    @Test
+    public void loadEFLinearFixedAll_ExceptionAtGetRecord() throws InterruptedException {
+        int efid = 0x4f30;
+        final CountDownLatch latch = new CountDownLatch(1);
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(9);
+                    AsyncResult.forMessage(response, null, new CommandException(
+                            CommandException.Error.OPERATION_NOT_ALLOWED));
+                    response.sendToTarget();
+                    mTestHandler.postDelayed(latch::countDown, 100);
+                    return null;
+                }).when(mCi).iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(),
+                anyInt(), isNull(), isNull(), isNull(), any(Message.class));
+
+        Message message = Message.obtain(mTestHandler);
+        mIccFileHandler.loadEFLinearFixedAll(efid, null, message);
+        mTestLooper.startAutoDispatch();
+        latch.await(5, java.util.concurrent.TimeUnit.SECONDS);
+        AsyncResult ar = (AsyncResult) message.obj;
+        assertTrue(ar.exception instanceof CommandException);
+        assertSame(CommandException.Error.OPERATION_NOT_ALLOWED,
+                ((CommandException) ar.exception).getCommandError());
+        assertNull(ar.result);
+    }
+
+    @Test
+    public void loadEFLinearFixedAll_ExceptionAtReadRecord() throws InterruptedException {
+        int efid = 0x4f30;
+        final CountDownLatch latch = new CountDownLatch(2);
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(9);
+                    String hexString = null;
+                    IccIoResult iir = null;
+                    if (response.what == 6) {
+                        hexString = "000000454F30040000FFFF01020145";
+                        iir = new IccIoResult(0x90, 0x00, IccUtils.hexStringToBytes(hexString));
+                        AsyncResult.forMessage(response, iir, null);
+                        latch.countDown();
+                    } else if (response.what == 7) {
+                        AsyncResult.forMessage(response, null, new CommandException(
+                                CommandException.Error.OPERATION_NOT_ALLOWED));
+                        mTestHandler.postDelayed(latch::countDown, 100);
+                    }
+                    response.sendToTarget();
+                    mTestHandler.postDelayed(latch::countDown, 100);
+                    return null;
+                }).when(mCi).iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(),
+                anyInt(),
+                isNull(), isNull(), isNull(), any(Message.class));
+
+        Message message = Message.obtain(mTestHandler);
+        mIccFileHandler.loadEFLinearFixedAll(efid, null, message);
+        mTestLooper.startAutoDispatch();
+        latch.await(5, java.util.concurrent.TimeUnit.SECONDS);
+        AsyncResult ar = (AsyncResult) message.obj;
+        assertTrue(ar.exception instanceof CommandException);
+        assertSame(CommandException.Error.OPERATION_NOT_ALLOWED,
+                ((CommandException) ar.exception).getCommandError());
+        assertNull(ar.result);
+    }
+
+    @Test
+    public void loadEFLinearFixedAll_FullLoop() throws InterruptedException {
+        int efid = 0x4f30;
+        final CountDownLatch latch = new CountDownLatch(2);
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(9);
+                    String hexString = null;
+                    if (response.what == 6) {
+                        hexString = "000000454F30040000FFFF01020145";
+                        latch.countDown();
+                    } else if (response.what == 7) {
+                        try {
+                            IccFileHandler.LoadLinearFixedContext lc =
+                                    (IccFileHandler.LoadLinearFixedContext) response.obj;
+                            if (mIccFileHandler.getEfid(lc) == efid) {
+                                hexString =
+                                        "A814C0034F3A01C1034F3202C5034F0904C9034F2109A90FC3034F611"
+                                                + "5C4034F1108CA034F5017AA0FC2034F4A03C7034F4B06CB"
+                                                + "034F4F16FFFFFFFFFFFFFFFFFFFFFFFFFF";
+                            }
+                            mTestHandler.postDelayed(latch::countDown, 100);
+
+                        } catch (Exception e) {
+                            e.printStackTrace();
+                            Log.e("UsimFH", e.getMessage());
+                        }
+
+                    }
+                    IccIoResult iir = new IccIoResult(0x90, 0x00,
+                            IccUtils.hexStringToBytes(hexString));
+                    AsyncResult.forMessage(response, iir, null);
+                    response.sendToTarget();
+                    return null;
+                })
+                .when(mCi)
+                .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(),
+                        isNull(), isNull(), isNull(), any(Message.class));
+
+        Message message = Message.obtain(mTestHandler);
+        mIccFileHandler.loadEFLinearFixedAll(efid, null, message);
+        mTestLooper.startAutoDispatch();
+        latch.await(5, java.util.concurrent.TimeUnit.SECONDS);
+        AsyncResult ar = (AsyncResult) message.obj;
+        assertNotNull(ar);
+        ArrayList<byte[]> results = (ArrayList<byte[]>) ar.result;
+        assertEquals(
+                "A814C0034F3A01C1034F3202C5034F0904C9034F2109A90FC3034F6115C4034F1108CA03"
+                        + "4F5017AA0FC2034F4A03C7034F4B06CB034F4F16FFFFFFFFFFFFFFFFFFFFFFFFFF",
+                IccUtils.bytesToHexString(results.get(0)));
+        verify(mCi, times(2)).iccIOForApp(anyInt(), anyInt(), anyString(),
+                anyInt(), anyInt(), anyInt(), isNull(), isNull(), isNull(), any(Message.class));
+    }
+
+    @Test
+    public void loadEFLinearFixedAll_WithNullPath() {
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(9);
+                    AsyncResult.forMessage(response, "Success", null);
+                    response.sendToTarget();
+                    return null;
+                })
+                .when(mCi)
+                .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(),
+                        isNull(), isNull(), isNull(), any(Message.class));
+
+        Message message = Message.obtain(mTestHandler);
+        mIccFileHandler.loadEFLinearFixedAll(0, "", message);
+        verify(mCi, times(1)).iccIOForApp(anyInt(), anyInt(), anyString(),
+                anyInt(), anyInt(), anyInt(), isNull(), isNull(), isNull(), any(Message.class));
+    }
+
+    @Test
+    public void loadEFLinearFixedAll() {
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(9);
+                    AsyncResult.forMessage(response, "Success", null);
+                    response.sendToTarget();
+                    return null;
+                })
+                .when(mCi)
+                .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(),
+                        isNull(), isNull(), isNull(), any(Message.class));
+
+        Message message = Message.obtain(mTestHandler);
+        mIccFileHandler.loadEFLinearFixedAll(0, message);
+        verify(mCi, times(1)).iccIOForApp(anyInt(), anyInt(), anyString(),
+                anyInt(), anyInt(), anyInt(), isNull(), isNull(), isNull(), any(Message.class));
+    }
+
+    @Test
+    public void loadEFTransparent() {
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(9);
+                    AsyncResult.forMessage(response, "Success", null);
+                    response.sendToTarget();
+                    return null;
+                })
+                .when(mCi)
+                .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(),
+                        isNull(), isNull(), isNull(), any(Message.class));
+
+        Message message = Message.obtain(mTestHandler);
+        mIccFileHandler.loadEFTransparent(0, message);
+        verify(mCi, times(1)).iccIOForApp(anyInt(), anyInt(), anyString(),
+                anyInt(), anyInt(), anyInt(), isNull(), isNull(), isNull(), any(Message.class));
+    }
+
+    @Test
+    public void loadEFTransparent_WithZeroSize() {
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(9);
+                    AsyncResult.forMessage(response, "Success", null);
+                    response.sendToTarget();
+                    return null;
+                })
+                .when(mCi)
+                .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(),
+                        isNull(), isNull(), isNull(), any(Message.class));
+
+        Message message = Message.obtain(mTestHandler);
+        mIccFileHandler.loadEFTransparent(0, 0, message);
+        verify(mCi, times(1)).iccIOForApp(anyInt(), anyInt(), anyString(),
+                anyInt(), anyInt(), anyInt(), isNull(), isNull(), isNull(), any(Message.class));
+    }
+
+    @Test
+    public void loadEFImgTransparent() {
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(9);
+                    AsyncResult.forMessage(response, "Success", null);
+                    response.sendToTarget();
+                    return null;
+                })
+                .when(mCi)
+                .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(),
+                        isNull(), isNull(), isNull(), any(Message.class));
+
+        Message message = Message.obtain(mTestHandler);
+        mIccFileHandler.loadEFImgTransparent(0, 0, 0, 0, message);
+        verify(mCi, times(1)).iccIOForApp(anyInt(), anyInt(), anyString(),
+                anyInt(), anyInt(), anyInt(), isNull(), isNull(), isNull(), any(Message.class));
+    }
+
+    @Test
+    public void updateEFLinearFixed_WithNullPath() {
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(9);
+                    AsyncResult.forMessage(response, "Success", null);
+                    response.sendToTarget();
+                    return null;
+                })
+                .when(mCi)
+                .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(),
+                        anyString(), isNull(), isNull(), any(Message.class));
+
+        Message message = Message.obtain(mTestHandler);
+        mIccFileHandler.updateEFLinearFixed(0, "", 0, new byte[10], null, message);
+        verify(mCi, times(1)).iccIOForApp(anyInt(), anyInt(), anyString(),
+                anyInt(), anyInt(), anyInt(), anyString(), isNull(), isNull(), any(Message.class));
+    }
+
+    @Test
+    public void updateEFLinearFixed() {
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(9);
+                    AsyncResult.forMessage(response, "Success", null);
+                    response.sendToTarget();
+                    return null;
+                })
+                .when(mCi)
+                .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(),
+                        anyString(), isNull(), isNull(), any(Message.class));
+
+        Message message = Message.obtain(mTestHandler);
+        mIccFileHandler.updateEFLinearFixed(0, 0, new byte[10], null, message);
+        verify(mCi, times(1)).iccIOForApp(anyInt(), anyInt(), anyString(),
+                anyInt(), anyInt(), anyInt(), anyString(), isNull(), isNull(), any(Message.class));
+    }
+
+    @Test
+    public void updateEFTransparent() {
+        doAnswer(
+                invocation -> {
+                    Message response = invocation.getArgument(9);
+                    AsyncResult.forMessage(response, "Success", null);
+                    response.sendToTarget();
+                    return null;
+                })
+                .when(mCi)
+                .iccIOForApp(anyInt(), anyInt(), anyString(), anyInt(), anyInt(), anyInt(),
+                        anyString(), isNull(), isNull(), any(Message.class));
+
+        Message message = Message.obtain(mTestHandler);
+        mIccFileHandler.updateEFTransparent(0, new byte[10], message);
+        verify(mCi, times(1)).iccIOForApp(anyInt(), anyInt(), anyString(),
+                anyInt(), anyInt(), anyInt(), anyString(), isNull(), isNull(), any(Message.class));
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/IccIoResultTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/IccIoResultTest.java
new file mode 100644
index 0000000..b3e0a85
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/IccIoResultTest.java
@@ -0,0 +1,452 @@
+/*
+ * Copyright (C) 2022 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.uicc;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class IccIoResultTest {
+
+    @Test
+    public void check0x90_ErrorCodeParsing() {
+        IccIoResult iccIoResult = new IccIoResult(0x90, 0x00, new byte[10]);
+        String resultStr = iccIoResult.toString();
+
+        Assert.assertTrue(resultStr != null && (!resultStr.contains("Error")));
+    }
+
+    @Test
+    public void check0x91_ErrorCodeParsing() {
+        IccIoResult iccIoResult = new IccIoResult(0x91, 0x00, new byte[10]);
+        String resultStr = iccIoResult.toString();
+
+        Assert.assertTrue(resultStr != null && (!resultStr.contains("Error")));
+    }
+
+    @Test
+    public void check0x9E_ErrorCodeParsing() {
+        IccIoResult iccIoResult = new IccIoResult(0x9E, 0x00, new byte[10]);
+        String resultStr = iccIoResult.toString();
+
+        Assert.assertTrue(resultStr != null && (!resultStr.contains("Error")));
+    }
+
+    @Test
+    public void check0x9F_ErrorCodeParsing() {
+        IccIoResult iccIoResult = new IccIoResult(0x9F, 0x00, new byte[10]);
+        String resultStr = iccIoResult.toString();
+
+        Assert.assertTrue(resultStr != null && (!resultStr.contains("Error")));
+    }
+
+    @Test
+    public void check0x94_ErrorCodeParsing() {
+        IccIoResult iccIoResult = new IccIoResult(0x94, 0x00, new byte[10]);
+        String resultStr = iccIoResult.toString();
+
+        Assert.assertTrue(resultStr != null && (resultStr.contains("no EF selected")));
+
+        iccIoResult = new IccIoResult(0x94, 0x02, new byte[10]);
+        resultStr = iccIoResult.toString();
+
+        Assert.assertTrue(
+                resultStr != null && (resultStr.contains("out f range (invalid address)")));
+
+        iccIoResult = new IccIoResult(0x94, 0x04, new byte[10]);
+        resultStr = iccIoResult.toString();
+
+        Assert.assertTrue(
+                resultStr != null && (resultStr.contains("file ID not found/pattern not found")));
+
+        iccIoResult = new IccIoResult(0x94, 0x08, new byte[10]);
+        resultStr = iccIoResult.toString();
+
+        Assert.assertTrue(
+                resultStr != null && (resultStr.contains("file is inconsistent with the command")));
+    }
+
+    @Test
+    public void check0x98_ErrorCodeParsing() {
+        IccIoResult iccIoResult = new IccIoResult(0x98, 0x00, new byte[10]);
+        String resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains("unknown")));
+
+        iccIoResult = new IccIoResult(0x98, 0x02, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains("no CHV initialized")));
+
+        iccIoResult = new IccIoResult(0x98, 0x04, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(
+                resultStr != null && (resultStr.contains("access condition not fulfilled")));
+
+        iccIoResult = new IccIoResult(0x98, 0x08, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(
+                resultStr != null && (resultStr.contains("in contradiction with CHV status")));
+
+        iccIoResult = new IccIoResult(0x98, 0x10, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains(
+                "in contradiction with invalidation status")));
+
+        iccIoResult = new IccIoResult(0x98, 0x40, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains(
+                "unsuccessful CHV verification, no attempt left")));
+
+        iccIoResult = new IccIoResult(0x98, 0x50, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains(
+                "increase cannot be performed, Max value reached")));
+
+        iccIoResult = new IccIoResult(0x98, 0x62, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains(
+                "authentication error, application specific")));
+
+        iccIoResult = new IccIoResult(0x98, 0x64, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains(
+                "authentication error, security context not supported")));
+
+        iccIoResult = new IccIoResult(0x98, 0x65, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains("key freshness failure")));
+
+        iccIoResult = new IccIoResult(0x98, 0x66, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains(
+                "authentication error, no memory space available")));
+
+        iccIoResult = new IccIoResult(0x98, 0x67, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains(
+                "authentication error, no memory space available in EF_MUK")));
+    }
+
+    @Test
+    public void check0x61_ErrorCodeParsing() {
+        IccIoResult iccIoResult = new IccIoResult(0x61, 0x20, new byte[10]);
+        String resultStr = iccIoResult.toString();
+        Assert.assertTrue(
+                resultStr != null && (resultStr.contains("more response bytes available")));
+    }
+
+    @Test
+    public void check0x62_ErrorCodeParsing() {
+        IccIoResult iccIoResult = new IccIoResult(0x62, 0x00, new byte[10]);
+        String resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains("no information given")));
+
+        iccIoResult = new IccIoResult(0x62, 0x81, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(
+                resultStr != null && (resultStr.contains(
+                        "part of returned data may be corrupted")));
+
+        iccIoResult = new IccIoResult(0x62, 0x82, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains(
+                "end of file/record reached before reading Le bytes")));
+
+        iccIoResult = new IccIoResult(0x62, 0x83, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains("selected file invalidated")));
+
+        iccIoResult = new IccIoResult(0x62, 0x84, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(
+                resultStr != null && (resultStr.contains("selected file in termination state")));
+
+        iccIoResult = new IccIoResult(0x62, 0xF1, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains("more data available")));
+
+        iccIoResult = new IccIoResult(0x62, 0xF2, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains(
+                "more data available and proactive command pending")));
+
+        iccIoResult = new IccIoResult(0x62, 0xF3, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains("response data available")));
+
+        iccIoResult = new IccIoResult(0x62, 0xF4, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains("unknown")));
+    }
+
+    @Test
+    public void check0x63_ErrorCodeParsing() {
+        IccIoResult iccIoResult = new IccIoResult(0x63, 0xC0, new byte[10]);
+        String resultStr = iccIoResult.toString();
+        Assert.assertTrue(
+                resultStr != null && (resultStr.contains(
+                        "command successful but after using an internal update retry routine 0 "
+                                + "times")));
+
+        iccIoResult = new IccIoResult(0x63, 0xF1, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(
+                resultStr != null && (resultStr.contains("more data expected")));
+
+        iccIoResult = new IccIoResult(0x63, 0xF2, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(
+                resultStr != null && (resultStr.contains(
+                        "more data expected and proactive command pending")));
+
+        iccIoResult = new IccIoResult(0x63, 0xF3, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(
+                resultStr != null && (resultStr.contains("unknown")));
+    }
+
+    @Test
+    public void check0x64_ErrorCodeParsing() {
+        IccIoResult iccIoResult = new IccIoResult(0x64, 0xC0, new byte[10]);
+        String resultStr = iccIoResult.toString();
+        Assert.assertTrue(
+                resultStr != null && (resultStr.contains("unknown")));
+
+        iccIoResult = new IccIoResult(0x64, 0x00, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(
+                resultStr != null && (resultStr.contains("no information given")));
+    }
+
+    @Test
+    public void check0x65_ErrorCodeParsing() {
+        IccIoResult iccIoResult = new IccIoResult(0x65, 0xC0, new byte[10]);
+        String resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains("unknown")));
+
+        iccIoResult = new IccIoResult(0x65, 0x00, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue((resultStr != null) && (resultStr.contains(
+                "no information given, state of non-volatile memory changed")));
+
+        iccIoResult = new IccIoResult(0x65, 0x81, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains("memory problem")));
+    }
+
+    @Test
+    public void check0x67_ErrorCodeParsing() {
+        IccIoResult iccIoResult = new IccIoResult(0x67, 0xC0, new byte[10]);
+        String resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains(
+                "the interpretation of this status word is command dependent")));
+
+        iccIoResult = new IccIoResult(0x67, 0x00, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains(
+                "incorrect parameter P3")));
+    }
+
+    @Test
+    public void check0x68_ErrorCodeParsing() {
+        IccIoResult iccIoResult = new IccIoResult(0x68, 0xC0, new byte[10]);
+        String resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains("unknown")));
+
+        iccIoResult = new IccIoResult(0x68, 0x00, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains("no information given")));
+
+        iccIoResult = new IccIoResult(0x68, 0x81, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(
+                (resultStr != null) && (resultStr.contains("logical channel not supported")));
+
+        iccIoResult = new IccIoResult(0x68, 0x82, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(
+                (resultStr != null) && (resultStr.contains("secure messaging not supported")));
+    }
+
+    @Test
+    public void check0x69_ErrorCodeParsing() {
+        IccIoResult iccIoResult = new IccIoResult(0x69, 0xC0, new byte[10]);
+        String resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains("unknown")));
+
+        iccIoResult = new IccIoResult(0x69, 0x00, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains("no information given")));
+
+        iccIoResult = new IccIoResult(0x69, 0x81, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(
+                (resultStr != null) && (resultStr.contains(
+                        "command incompatible with file structure")));
+
+        iccIoResult = new IccIoResult(0x69, 0x82, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(
+                (resultStr != null) && (resultStr.contains("security status not satisfied")));
+
+        iccIoResult = new IccIoResult(0x69, 0x83, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(
+                (resultStr != null) && (resultStr.contains("authentication/PIN method blocked")));
+
+        iccIoResult = new IccIoResult(0x69, 0x84, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(
+                (resultStr != null) && (resultStr.contains("referenced data invalidated")));
+
+        iccIoResult = new IccIoResult(0x69, 0x85, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(
+                (resultStr != null) && (resultStr.contains("conditions of use not satisfied")));
+
+        iccIoResult = new IccIoResult(0x69, 0x86, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue((resultStr != null) && (resultStr.contains(
+                "command not allowed (no EF selected)")));
+
+        iccIoResult = new IccIoResult(0x69, 0x89, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue((resultStr != null) && (resultStr.contains(
+                "command not allowed - secure channel - security not satisfied")));
+    }
+
+    @Test
+    public void check0x6A_ErrorCodeParsing() {
+        IccIoResult iccIoResult = new IccIoResult(0x6A, 0xC0, new byte[10]);
+        String resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains("unknown")));
+
+        iccIoResult = new IccIoResult(0x6A, 0x80, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains(
+                "incorrect parameters in the data field")));
+
+        iccIoResult = new IccIoResult(0x6A, 0x81, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(
+                (resultStr != null) && (resultStr.contains("function not supported")));
+
+        iccIoResult = new IccIoResult(0x6A, 0x82, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(
+                (resultStr != null) && (resultStr.contains("file not found")));
+
+        iccIoResult = new IccIoResult(0x6A, 0x83, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(
+                (resultStr != null) && (resultStr.contains("record not found")));
+
+        iccIoResult = new IccIoResult(0x6A, 0x84, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(
+                (resultStr != null) && (resultStr.contains("not enough memory space")));
+
+        iccIoResult = new IccIoResult(0x6A, 0x86, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(
+                (resultStr != null) && (resultStr.contains("incorrect parameters P1 to P2")));
+
+        iccIoResult = new IccIoResult(0x6A, 0x87, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue((resultStr != null) && (resultStr.contains(
+                "lc inconsistent with P1 to P2")));
+
+        iccIoResult = new IccIoResult(0x6A, 0x88, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue((resultStr != null) && (resultStr.contains(
+                "referenced data not found")));
+    }
+
+    @Test
+    public void check0x6B_ErrorCodeParsing() {
+        IccIoResult iccIoResult = new IccIoResult(0x6B, 0xC0, new byte[10]);
+        String resultStr = iccIoResult.toString();
+        Assert.assertTrue(
+                resultStr != null && (resultStr.contains("incorrect parameter P1 or P2")));
+    }
+
+    @Test
+    public void check0x6C_ErrorCodeParsing() {
+        IccIoResult iccIoResult = new IccIoResult(0x6C, 0xC0, new byte[10]);
+        String resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains("wrong length, retry with ")));
+    }
+
+    @Test
+    public void check0x6D_ErrorCodeParsing() {
+        IccIoResult iccIoResult = new IccIoResult(0x6D, 0xC0, new byte[10]);
+        String resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains(
+                "unknown instruction code given in the command")));
+    }
+
+    @Test
+    public void check0x6E_ErrorCodeParsing() {
+        IccIoResult iccIoResult = new IccIoResult(0x6E, 0xC0, new byte[10]);
+        String resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains(
+                "wrong instruction class given in the command")));
+    }
+
+    @Test
+    public void check0x6F_ErrorCodeParsing() {
+        IccIoResult iccIoResult = new IccIoResult(0x6F, 0xC0, new byte[10]);
+        String resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains(
+                "the interpretation of this status word is command dependent")));
+
+        iccIoResult = new IccIoResult(0x6F, 0x00, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains(
+                "technical problem with no diagnostic given")));
+    }
+
+    @Test
+    public void check0x92_ErrorCodeParsing() {
+        IccIoResult iccIoResult = new IccIoResult(0x92, 0x00, new byte[10]);
+        String resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains(
+                "command successful but after using an internal update retry routine")));
+
+        iccIoResult = new IccIoResult(0x92, 0x40, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains(
+                "memory problem")));
+
+        iccIoResult = new IccIoResult(0x92, 0x41, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains(
+                "unknown")));
+    }
+
+    @Test
+    public void check0x93_ErrorCodeParsing() {
+        IccIoResult iccIoResult = new IccIoResult(0x93, 0x00, new byte[10]);
+        String resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains(
+                "SIM Application Toolkit is busy. Command cannot be executed"
+                        + " at present, further normal commands are allowed")));
+
+        iccIoResult = new IccIoResult(0x93, 0x41, new byte[10]);
+        resultStr = iccIoResult.toString();
+        Assert.assertTrue(resultStr != null && (resultStr.contains(
+                "unknown")));
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/IccPhoneBookInterfaceManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/IccPhoneBookInterfaceManagerTest.java
old mode 100755
new mode 100644
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/IsimUiccRecordsTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/IsimUiccRecordsTest.java
index 8324a5b..6f4666c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/IsimUiccRecordsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/IsimUiccRecordsTest.java
@@ -303,4 +303,77 @@
         assertTrue(((CommandException) ar.exception).getCommandError() ==
                 CommandException.Error.OPERATION_NOT_ALLOWED);
     }
-}
+
+    @Test
+    public void testGetSimServiceTable() {
+        // reading sim service table successfully case
+        byte[] sst = new byte[9];
+        for (int i = 0; i < sst.length; i++) {
+            if (i % 2 == 0) {
+                sst[i] = 0;
+            } else {
+                sst[i] = 1;
+            }
+        }
+        Message message = mIsimUiccRecordsUT.obtainMessage(
+                IccRecords.EVENT_GET_ICC_RECORD_DONE, mIsimUiccRecordsUT.getIsimIstObject());
+        AsyncResult ar = AsyncResult.forMessage(message, sst, null);
+        mIsimUiccRecordsUT.handleMessage(message);
+        String mockSst = IccUtils.bytesToHexString(sst);
+        String resultSst = mIsimUiccRecordsUT.getIsimIst();
+        assertEquals(mockSst, resultSst);
+    }
+
+    @Test
+    public void testGetSimServiceTableException() {
+        // sim service table exception handling case
+        Message message = mIsimUiccRecordsUT.obtainMessage(
+                IccRecords.EVENT_GET_ICC_RECORD_DONE, mIsimUiccRecordsUT.getIsimIstObject());
+        AsyncResult ar = AsyncResult.forMessage(message,  null, new CommandException(
+                CommandException.Error.OPERATION_NOT_ALLOWED));
+        mIsimUiccRecordsUT.handleMessage(message);
+        String resultSst = mIsimUiccRecordsUT.getIsimIst();
+        assertEquals(null, resultSst);
+    }
+
+    @Test
+    public void testGetSsimServiceTableLessTableSize() {
+        // The less IST table size will not give any problem
+        byte[] sst = new byte[5];
+        for (int i = 0; i < sst.length; i++) {
+            if (i % 2 == 0) {
+                sst[i] = 0;
+            } else {
+                sst[i] = 1;
+            }
+        }
+        Message message = mIsimUiccRecordsUT.obtainMessage(
+                IccRecords.EVENT_GET_ICC_RECORD_DONE, mIsimUiccRecordsUT.getIsimIstObject());
+        AsyncResult ar = AsyncResult.forMessage(message, sst, null);
+        mIsimUiccRecordsUT.handleMessage(message);
+        String mockSst = IccUtils.bytesToHexString(sst);
+        String resultSst = mIsimUiccRecordsUT.getIsimIst();
+        assertEquals(mockSst, resultSst);
+    }
+
+    @Test
+    public void testGetSsimServiceTableLargeTableSize() {
+        // The Big IST table size will not give any problem [ in feature the table may grows]
+        byte[] sst = new byte[99];
+        for (int i = 0; i < sst.length; i++) {
+            if (i % 2 == 0) {
+                sst[i] = 0;
+            } else {
+                sst[i] = 1;
+            }
+        }
+        Message message = mIsimUiccRecordsUT.obtainMessage(
+                IccRecords.EVENT_GET_ICC_RECORD_DONE, mIsimUiccRecordsUT.getIsimIstObject());
+        AsyncResult ar = AsyncResult.forMessage(message, sst, null);
+        mIsimUiccRecordsUT.handleMessage(message);
+        String mockSst = IccUtils.bytesToHexString(sst);
+        String resultSst = mIsimUiccRecordsUT.getIsimIst();
+        assertEquals(mockSst, resultSst);
+    }
+
+}
\ No newline at end of file
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/PortUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/PortUtilsTest.java
new file mode 100644
index 0000000..69d9a7d
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/PortUtilsTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2016 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.uicc;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class PortUtilsTest extends TelephonyTest {
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        doReturn(IccSlotStatus.MultipleEnabledProfilesMode.NONE)
+                .when(mUiccController).getSupportedMepMode(anyInt());
+        processAllMessages();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    public void testConvertToHalPortIndex() {
+        assertEquals(0, PortUtils.convertToHalPortIndex(0, 0));
+        doReturn(IccSlotStatus.MultipleEnabledProfilesMode.MEP_A1)
+                .when(mUiccController).getSupportedMepMode(anyInt());
+        assertEquals(1, PortUtils.convertToHalPortIndex(0, 0));
+    }
+
+    @Test
+    public void testConvertFromHalPortIndex() {
+        assertEquals(0, PortUtils.convertFromHalPortIndex(0, 1,
+                IccCardStatus.CardState.CARDSTATE_PRESENT,
+                IccSlotStatus.MultipleEnabledProfilesMode.MEP_A1));
+        assertEquals(1, PortUtils.convertFromHalPortIndex(0, 1,
+                IccCardStatus.CardState.CARDSTATE_PRESENT,
+                IccSlotStatus.MultipleEnabledProfilesMode.MEP_B));
+        assertEquals(1, PortUtils.convertFromHalPortIndex(0, 1,
+                IccCardStatus.CardState.CARDSTATE_ABSENT,
+                IccSlotStatus.MultipleEnabledProfilesMode.MEP_A1));
+        doReturn(IccSlotStatus.MultipleEnabledProfilesMode.MEP_A1)
+                .when(mUiccController).getSupportedMepMode(anyInt());
+        assertEquals(0, PortUtils.convertFromHalPortIndex(0, 1,
+                IccCardStatus.CardState.CARDSTATE_ABSENT,
+                IccSlotStatus.MultipleEnabledProfilesMode.NONE));
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/RuimRecordsTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/RuimRecordsTest.java
old mode 100755
new mode 100644
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/SIMRecordsTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/SIMRecordsTest.java
index 3d6f770..e109ebb 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/SIMRecordsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/SIMRecordsTest.java
@@ -22,9 +22,11 @@
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.content.Context;
@@ -32,6 +34,7 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.test.TestLooper;
+import android.util.Log;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -41,6 +44,7 @@
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.uicc.IccRecords.OperatorPlmnInfo;
 import com.android.internal.telephony.uicc.IccRecords.PlmnNetworkName;
+import com.android.telephony.Rlog;
 
 import org.junit.After;
 import org.junit.Before;
@@ -50,6 +54,9 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
 public class SIMRecordsTest extends TelephonyTest {
@@ -59,6 +66,7 @@
     private static final List<String> EMPTY_FPLMN_LIST = new ArrayList<>();
     private static final int EF_SIZE = 12;
     private static final int MAX_NUM_FPLMN = 4;
+    private static final int SET_VOICE_MAIL_TIMEOUT = 1000;
 
     // Mocked classes
     private IccFileHandler mFhMock;
@@ -597,7 +605,6 @@
         data[5] = (byte) (lacTacEnd >>> 8);
         data[6] = (byte) lacTacEnd;
         data[7] = (byte) pnnIndex;
-
         return data;
     }
 
@@ -668,4 +675,300 @@
         assertEquals(null, mSIMRecordsUT.getSmscIdentity());
         assertTrue(ar.exception instanceof CommandException);
     }
+
+    @Test
+    public void testGetSimServiceTable() {
+        // reading sim service table successfully case
+        byte[] sst = new byte[111];
+        for (int i = 0; i < sst.length; i++) {
+            if (i % 2 == 0) {
+                sst[i] = 0;
+            } else {
+                sst[i] = 1;
+            }
+        }
+        Message message = mSIMRecordsUT.obtainMessage(SIMRecords.EVENT_GET_SST_DONE);
+        AsyncResult ar = AsyncResult.forMessage(message, sst, null);
+        mSIMRecordsUT.handleMessage(message);
+        String mockSst = IccUtils.bytesToHexString(sst);
+        String resultSst = mSIMRecordsUT.getSimServiceTable();
+        assertEquals(mockSst, resultSst);
+    }
+
+    @Test
+    public void testGetSimServiceTableException() {
+        // sim service table exception handling case
+        Message message = mSIMRecordsUT.obtainMessage(SIMRecords.EVENT_GET_SST_DONE);
+        AsyncResult ar = AsyncResult.forMessage(message, null, new CommandException(
+                CommandException.Error.OPERATION_NOT_ALLOWED));
+        mSIMRecordsUT.handleMessage(message);
+        String resultSst = mSIMRecordsUT.getSimServiceTable();
+        assertEquals(null, resultSst);
+    }
+
+    @Test
+    public void testGetSsimServiceTableLessTableSize() {
+        // sim service table reading case
+        byte[] sst = new byte[12];
+        for (int i = 0; i < sst.length; i++) {
+            if (i % 2 == 0) {
+                sst[i] = 0;
+            } else {
+                sst[i] = 1;
+            }
+        }
+        Message message = mSIMRecordsUT.obtainMessage(SIMRecords.EVENT_GET_SST_DONE);
+        AsyncResult ar = AsyncResult.forMessage(message, sst, null);
+        mSIMRecordsUT.handleMessage(message);
+        String mockSst = IccUtils.bytesToHexString(sst);
+        String resultSst = mSIMRecordsUT.getSimServiceTable();
+        assertEquals(mockSst, resultSst);
+    }
+
+    @Test
+    public void testSetVoiceMailNumber() throws InterruptedException {
+        String voiceMailNumber = "1234567890";
+        String alphaTag = "Voicemail";
+        final CountDownLatch latch = new CountDownLatch(2);
+        doAnswer(invocation -> {
+            int[] result = new int[3];
+            result[0] = 32;
+            result[1] = 32;
+            result[2] = 1;
+            Rlog.d("SIMRecordsTest", "Executing the first invocation");
+            Message response = invocation.getArgument(2);
+            AsyncResult.forMessage(response, result, null);
+            response.sendToTarget();
+            latch.countDown();
+            return null;
+        }).when(mFhMock).getEFLinearRecordSize(anyInt(), isNull(), any(Message.class));
+
+        doAnswer(invocation -> {
+            int[] result = new int[3];
+            result[0] = 32;
+            result[1] = 32;
+            result[2] = 1;
+            Rlog.d("SIMRecordsTest", "Executing the second invocation");
+            Message response = invocation.getArgument(5);
+            AsyncResult.forMessage(response, result, null);
+            response.sendToTarget();
+            latch.countDown();
+            return null;
+        }).when(mFhMock).updateEFLinearFixed(anyInt(), eq(null), anyInt(), any(byte[].class),
+                eq(null), any(Message.class));
+
+        mSIMRecordsUT.setMailboxIndex(1);
+        Message message = Message.obtain(mTestHandler);
+        mSIMRecordsUT.setVoiceMailNumber(alphaTag, voiceMailNumber, message);
+        latch.await(5, TimeUnit.SECONDS);
+        mTestLooper.startAutoDispatch();
+        verify(mFhMock, times(1)).getEFLinearRecordSize(anyInt(), isNull(), any(Message.class));
+        verify(mFhMock, times(1)).updateEFLinearFixed(anyInt(), eq(null), anyInt(),
+                any(byte[].class), eq(null), any(Message.class));
+        waitUntilConditionIsTrueOrTimeout(new Condition() {
+            @Override
+            public Object expected() {
+                return true;
+            }
+
+            @Override
+            public Object actual() {
+                return mSIMRecordsUT.getVoiceMailNumber() != null;
+            }
+        });
+        assertEquals(voiceMailNumber, mSIMRecordsUT.getVoiceMailNumber());
+        assertEquals(alphaTag, mSIMRecordsUT.getVoiceMailAlphaTag());
+    }
+
+    @Test
+    public void testSetVoiceMailNumberBigAlphatag() throws InterruptedException {
+        String voiceMailNumber = "1234567890";
+        String alphaTag = "VoicemailAlphaTag-VoicemailAlphaTag";
+        final CountDownLatch latch = new CountDownLatch(2);
+        doAnswer(invocation -> {
+            int[] result = new int[3];
+            result[0] = 32;
+            result[1] = 32;
+            result[2] = 1;
+            Rlog.d("SIMRecordsTest", "Executing the first invocation");
+            Message response = invocation.getArgument(2);
+            AsyncResult.forMessage(response, result, null);
+            response.sendToTarget();
+            latch.countDown();
+            return null;
+        }).when(mFhMock).getEFLinearRecordSize(anyInt(), isNull(), any(Message.class));
+
+        doAnswer(invocation -> {
+            int[] result = new int[3];
+            result[0] = 32;
+            result[1] = 32;
+            result[2] = 1;
+            Rlog.d("SIMRecordsTest", "Executing the second invocation");
+            Message response = invocation.getArgument(5);
+            AsyncResult.forMessage(response, result, null);
+            response.sendToTarget();
+            latch.countDown();
+            return null;
+        }).when(mFhMock).updateEFLinearFixed(anyInt(), eq(null), anyInt(), any(byte[].class),
+                eq(null), any(Message.class));
+
+        mSIMRecordsUT.setMailboxIndex(1);
+        Message message = Message.obtain(mTestHandler);
+        mSIMRecordsUT.setVoiceMailNumber(alphaTag, voiceMailNumber, message);
+        latch.await(8, TimeUnit.SECONDS);
+        mTestLooper.startAutoDispatch();
+        verify(mFhMock, times(1)).getEFLinearRecordSize(anyInt(), isNull(), any(Message.class));
+        verify(mFhMock, times(1)).updateEFLinearFixed(anyInt(), eq(null), anyInt(),
+                any(byte[].class), eq(null), any(Message.class));
+        //if attempt to save bugAlphatag which sim don't support so we will make it null
+        waitUntilConditionIsTrueOrTimeout(new Condition() {
+            @Override
+            public Object expected() {
+                return true;
+            }
+
+            @Override
+            public Object actual() {
+                return mSIMRecordsUT.getVoiceMailNumber() != null;
+            }
+        });
+        assertEquals(null, mSIMRecordsUT.getVoiceMailAlphaTag());
+        assertEquals(voiceMailNumber, mSIMRecordsUT.getVoiceMailNumber());
+    }
+
+    @Test
+    public void testSetVoiceMailNumberUtf16Alphatag() throws InterruptedException {
+        String voiceMailNumber = "1234567890";
+        String alphaTag = "หมายเลขข้อความเสียง"; // Messagerie vocale
+        final CountDownLatch latch = new CountDownLatch(2);
+        doAnswer(invocation -> {
+            int[] result = new int[3];
+            result[0] = 32;
+            result[1] = 32;
+            result[2] = 1;
+            Rlog.d("SIMRecordsTest", "Executing the first invocation");
+            Message response = invocation.getArgument(2);
+            AsyncResult.forMessage(response, result, null);
+            response.sendToTarget();
+            latch.countDown();
+            return null;
+        }).when(mFhMock).getEFLinearRecordSize(anyInt(), isNull(), any(Message.class));
+
+        doAnswer(invocation -> {
+            int[] result = new int[3];
+            result[0] = 32;
+            result[1] = 32;
+            result[2] = 1;
+            Rlog.d("SIMRecordsTest", "Executing the second invocation");
+            Message response = invocation.getArgument(5);
+            AsyncResult.forMessage(response, result, null);
+            response.sendToTarget();
+            latch.countDown();
+            return null;
+        }).when(mFhMock).updateEFLinearFixed(anyInt(), eq(null), anyInt(), any(byte[].class),
+                eq(null), any(Message.class));
+
+        mSIMRecordsUT.setMailboxIndex(1);
+        Message message = Message.obtain(mTestHandler);
+        mSIMRecordsUT.setVoiceMailNumber(alphaTag, voiceMailNumber, message);
+        latch.await(5, TimeUnit.SECONDS);
+
+        mTestLooper.startAutoDispatch();
+        verify(mFhMock, times(1)).getEFLinearRecordSize(anyInt(), isNull(), any(Message.class));
+        verify(mFhMock, times(1)).updateEFLinearFixed(anyInt(), eq(null), anyInt(),
+                any(byte[].class), eq(null), any(Message.class));
+        waitUntilConditionIsTrueOrTimeout(new Condition() {
+            @Override
+            public Object expected() {
+                return true;
+            }
+
+            @Override
+            public Object actual() {
+                return mSIMRecordsUT.getVoiceMailNumber() != null;
+            }
+        });
+        assertEquals(voiceMailNumber, mSIMRecordsUT.getVoiceMailNumber());
+        //if attempt to save bugAlphatag which sim don't support so we will make it null
+        assertEquals(null, mSIMRecordsUT.getVoiceMailAlphaTag());
+    }
+
+    @Test
+    public void testSetVoiceMailNullNumber() throws InterruptedException {
+        String voiceMailNumber = null;
+        String alphaTag = "VoicemailAlphaTag"; // Messagerie vocale
+        final CountDownLatch latch = new CountDownLatch(2);
+        doAnswer(invocation -> {
+            int[] result = new int[3];
+            result[0] = 32;
+            result[1] = 32;
+            result[2] = 1;
+            Rlog.d("SIMRecordsTest", "Executing the first invocation");
+            Message response = invocation.getArgument(2);
+            AsyncResult.forMessage(response, result, null);
+            response.sendToTarget();
+            latch.countDown();
+            return null;
+        }).when(mFhMock).getEFLinearRecordSize(anyInt(), isNull(), any(Message.class));
+
+        doAnswer(invocation -> {
+            int[] result = new int[3];
+            result[0] = 32;
+            result[1] = 32;
+            result[2] = 1;
+            Rlog.d("SIMRecordsTest", "Executing the second invocation");
+            Message response = invocation.getArgument(5);
+            AsyncResult.forMessage(response, result, null);
+            response.sendToTarget();
+            latch.countDown();
+            return null;
+        }).when(mFhMock).updateEFLinearFixed(anyInt(), eq(null), anyInt(), any(byte[].class),
+                eq(null), any(Message.class));
+
+        mSIMRecordsUT.setMailboxIndex(1);
+        Message message = Message.obtain(mTestHandler);
+        mSIMRecordsUT.setVoiceMailNumber(alphaTag, voiceMailNumber, message);
+        latch.await(5, TimeUnit.SECONDS);
+        mTestLooper.startAutoDispatch();
+        verify(mFhMock, times(1)).getEFLinearRecordSize(anyInt(), isNull(), any(Message.class));
+        verify(mFhMock, times(1)).updateEFLinearFixed(anyInt(), eq(null), anyInt(),
+                any(byte[].class), eq(null), any(Message.class));
+        waitUntilConditionIsTrueOrTimeout(new Condition() {
+            @Override
+            public Object expected() {
+                return true;
+            }
+
+            @Override
+            public Object actual() {
+                return mSIMRecordsUT.getVoiceMailAlphaTag() != null;
+            }
+        });
+        assertEquals(null, mSIMRecordsUT.getVoiceMailNumber());
+        assertEquals(alphaTag, mSIMRecordsUT.getVoiceMailAlphaTag());
+    }
+
+    public interface Condition {
+        Object expected();
+
+        Object actual();
+    }
+
+    protected void sleep(long ms) {
+        try {
+            Thread.sleep(ms);
+        } catch (Exception e) {
+            Log.d(TAG, "InterruptedException");
+        }
+    }
+
+    protected void waitUntilConditionIsTrueOrTimeout(Condition condition) {
+        final long start = System.currentTimeMillis();
+        while (!Objects.equals(condition.expected(), condition.actual())
+                && System.currentTimeMillis() - start
+                < (long) SIMRecordsTest.SET_VOICE_MAIL_TIMEOUT) {
+            sleep(50);
+        }
+        assertEquals("Service Unbound", condition.expected(), condition.actual());
+    }
 }
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 b30c3a7..e4fabc5 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardTest.java
@@ -61,7 +61,7 @@
         mIccIoResult = new IccIoResult(0x90, 0x00, IccUtils.hexStringToBytes("FF40"));
         mSimulatedCommands.setIccIoResultForApduLogicalChannel(mIccIoResult);
         mUiccCard = new UiccCard(mContext, mSimulatedCommands, mIccCardStatus, 0 /* phoneId */,
-            new Object(), false);
+            new Object(), IccSlotStatus.MultipleEnabledProfilesMode.NONE);
         processAllMessages();
         logd("create UiccCard");
     }
@@ -97,4 +97,10 @@
         assertNull(mUiccCard.getUiccPort(INVALID_PORT_ID));
         assertNotNull(mUiccCard.getUiccPort(TelephonyManager.DEFAULT_PORT_INDEX));
     }
+
+    @Test
+    @SmallTest
+    public void testGetCardId() {
+        assertNull(mUiccCard.getCardId());
+    }
 }
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 3deb501..143d7c9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRulesTest.java
@@ -85,7 +85,7 @@
         doAnswer(new Answer<Void>() {
             @Override
             public Void answer(InvocationOnMock invocation) throws Throwable {
-                Message message = (Message) invocation.getArguments()[7];
+                Message message = (Message) invocation.getArguments()[8];
                 IccIoResult iir = new IccIoResult(0x90, 0x00, IccUtils.hexStringToBytes(hexString));
                 AsyncResult ar = new AsyncResult(null, iir, null);
                 message.obj = ar;
@@ -93,16 +93,16 @@
                 return null;
             }
         }).when(mUiccProfile).iccTransmitApduLogicalChannel(anyInt(), anyInt(), anyInt(), anyInt(),
-                anyInt(), anyInt(), anyString(), any(Message.class));
+                anyInt(), anyInt(), anyString(), eq(false) /*isEs10Command*/, any(Message.class));
 
         doAnswer(new Answer<Void>() {
             @Override
             public Void answer(InvocationOnMock invocation) throws Throwable {
-                Message message = (Message) invocation.getArguments()[1];
+                Message message = (Message) invocation.getArguments()[2];
                 message.sendToTarget();
                 return null;
             }
-        }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), any(Message.class));
+        }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), eq(false), any(Message.class));
 
         mUiccCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(mUiccProfile, null);
         processAllMessages();
@@ -327,7 +327,7 @@
         doAnswer(new Answer<Void>() {
             @Override
             public Void answer(InvocationOnMock invocation) throws Throwable {
-                Message message = (Message) invocation.getArguments()[7];
+                Message message = (Message) invocation.getArguments()[8];
                 IccIoResult iir = new IccIoResult(0x90, 0x00, IccUtils.hexStringToBytes(hexString));
                 AsyncResult ar = new AsyncResult(null, iir, null);
                 message.obj = ar;
@@ -335,16 +335,16 @@
                 return null;
             }
         }).when(mUiccProfile).iccTransmitApduLogicalChannel(anyInt(), anyInt(), anyInt(), anyInt(),
-                anyInt(), anyInt(), anyString(), any(Message.class));
+                anyInt(), anyInt(), anyString(), eq(false) /*isEs10Command*/, any(Message.class));
 
         doAnswer(new Answer<Void>() {
             @Override
             public Void answer(InvocationOnMock invocation) throws Throwable {
-                Message message = (Message) invocation.getArguments()[1];
+                Message message = (Message) invocation.getArguments()[2];
                 message.sendToTarget();
                 return null;
             }
-        }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), any(Message.class));
+        }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), eq(false), any(Message.class));
 
 
         mUiccCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(mUiccProfile, null);
@@ -388,7 +388,7 @@
         doAnswer(new Answer<Void>() {
             @Override
             public Void answer(InvocationOnMock invocation) throws Throwable {
-                Message message = (Message) invocation.getArguments()[7];
+                Message message = (Message) invocation.getArguments()[8];
                 IccIoResult iir = new IccIoResult(0x90, 0x00, IccUtils.hexStringToBytes(hexString));
                 AsyncResult ar = new AsyncResult(null, iir, null);
                 message.obj = ar;
@@ -396,16 +396,16 @@
                 return null;
             }
         }).when(mUiccProfile).iccTransmitApduLogicalChannel(anyInt(), anyInt(), anyInt(), anyInt(),
-                anyInt(), anyInt(), anyString(), any(Message.class));
+                anyInt(), anyInt(), anyString(), eq(false) /*isEs10Command*/, any(Message.class));
 
         doAnswer(new Answer<Void>() {
             @Override
             public Void answer(InvocationOnMock invocation) throws Throwable {
-                Message message = (Message) invocation.getArguments()[1];
+                Message message = (Message) invocation.getArguments()[2];
                 message.sendToTarget();
                 return null;
             }
-        }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), any(Message.class));
+        }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), eq(false), any(Message.class));
 
         mUiccCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(mUiccProfile, null);
         processAllMessages();
@@ -445,7 +445,7 @@
         doAnswer(new Answer<Void>() {
             @Override
             public Void answer(InvocationOnMock invocation) throws Throwable {
-                Message message = (Message) invocation.getArguments()[7];
+                Message message = (Message) invocation.getArguments()[8];
                 IccIoResult iir = new IccIoResult(0x90, 0x00, IccUtils.hexStringToBytes(hexString));
                 AsyncResult ar = new AsyncResult(null, iir, null);
                 message.obj = ar;
@@ -453,16 +453,16 @@
                 return null;
             }
         }).when(mUiccProfile).iccTransmitApduLogicalChannel(anyInt(), anyInt(), anyInt(), anyInt(),
-                anyInt(), anyInt(), anyString(), any(Message.class));
+                anyInt(), anyInt(), anyString(), eq(false /*isEs10Command*/), any(Message.class));
 
         doAnswer(new Answer<Void>() {
             @Override
             public Void answer(InvocationOnMock invocation) throws Throwable {
-                Message message = (Message) invocation.getArguments()[1];
+                Message message = (Message) invocation.getArguments()[2];
                 message.sendToTarget();
                 return null;
             }
-        }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), any(Message.class));
+        }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), eq(false), any(Message.class));
 
         mUiccCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(mUiccProfile, null);
         processAllMessages();
@@ -509,11 +509,11 @@
         doAnswer(new Answer<Void>() {
             @Override
             public Void answer(InvocationOnMock invocation) throws Throwable {
-                Message message = (Message) invocation.getArguments()[1];
+                Message message = (Message) invocation.getArguments()[2];
                 message.sendToTarget();
                 return null;
             }
-        }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), any(Message.class));
+        }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), eq(false), any(Message.class));
 
         mUiccCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(mUiccProfile, null);
         processAllMessages();
@@ -569,14 +569,14 @@
             @Override
             public Void answer(InvocationOnMock invocation) throws Throwable {
                 currentFileId.set((String) invocation.getArguments()[6]);
-                Message message = (Message) invocation.getArguments()[7];
+                Message message = (Message) invocation.getArguments()[8];
                 AsyncResult ar = new AsyncResult(null, new int[]{2}, null);
                 message.obj = ar;
                 message.sendToTarget();
                 return null;
             }
         }).when(mUiccProfile).iccTransmitApduLogicalChannel(anyInt(), eq(0x00), eq(0xA4), eq(0x00),
-                eq(0x04), eq(0x02), anyString(), any(Message.class));
+                eq(0x04), eq(0x02), anyString(), eq(false /*isEs10Command*/), any(Message.class));
 
         // Read binary - since params are identical across files, we need to keep track of which
         // file was selected most recently and give back that content.
@@ -597,7 +597,7 @@
         doAnswer(new Answer<Void>() {
             @Override
             public Void answer(InvocationOnMock invocation) throws Throwable {
-                Message message = (Message) invocation.getArguments()[7];
+                Message message = (Message) invocation.getArguments()[8];
                 IccIoResult iir =
                         new IccIoResult(0x90, 0x00,
                                 IccUtils.hexStringToBytes(binaryContent.get(currentFileId.get())));
@@ -607,16 +607,16 @@
                 return null;
             }
         }).when(mUiccProfile).iccTransmitApduLogicalChannel(anyInt(), eq(0x00), eq(0xB0), eq(0x00),
-                eq(0x00), eq(0x00), eq(""), any(Message.class));
+                eq(0x00), eq(0x00), eq(""), eq(false /*isEs10Command*/), any(Message.class));
 
         doAnswer(new Answer<Void>() {
             @Override
             public Void answer(InvocationOnMock invocation) throws Throwable {
-                Message message = (Message) invocation.getArguments()[1];
+                Message message = (Message) invocation.getArguments()[2];
                 message.sendToTarget();
                 return null;
             }
-        }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), any(Message.class));
+        }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), eq(false), any(Message.class));
 
         mUiccCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(mUiccProfile, null);
         processAllMessages();
@@ -667,14 +667,14 @@
             @Override
             public Void answer(InvocationOnMock invocation) throws Throwable {
                 currentFileId.set((String) invocation.getArguments()[6]);
-                Message message = (Message) invocation.getArguments()[7];
+                Message message = (Message) invocation.getArguments()[8];
                 AsyncResult ar = new AsyncResult(null, new int[]{2}, null);
                 message.obj = ar;
                 message.sendToTarget();
                 return null;
             }
         }).when(mUiccProfile).iccTransmitApduLogicalChannel(anyInt(), eq(0x00), eq(0xA4), eq(0x00),
-                eq(0x04), eq(0x02), anyString(), any(Message.class));
+                eq(0x04), eq(0x02), anyString(), eq(false /*isEs10Command*/), any(Message.class));
 
         // Read binary - since params are identical across files, we need to keep track of which
         // file was selected most recently and give back that content.
@@ -690,7 +690,7 @@
         doAnswer(new Answer<Void>() {
             @Override
             public Void answer(InvocationOnMock invocation) throws Throwable {
-                Message message = (Message) invocation.getArguments()[7];
+                Message message = (Message) invocation.getArguments()[8];
                 IccIoResult iir =
                         new IccIoResult(0x90, 0x00,
                                 IccUtils.hexStringToBytes(binaryContent.get(currentFileId.get())));
@@ -700,16 +700,16 @@
                 return null;
             }
         }).when(mUiccProfile).iccTransmitApduLogicalChannel(anyInt(), eq(0x00), eq(0xB0), eq(0x00),
-                eq(0x00), eq(0x00), eq(""), any(Message.class));
+                eq(0x00), eq(0x00), eq(""), eq(false /*isEs10Command*/), any(Message.class));
 
         doAnswer(new Answer<Void>() {
             @Override
             public Void answer(InvocationOnMock invocation) throws Throwable {
-                Message message = (Message) invocation.getArguments()[1];
+                Message message = (Message) invocation.getArguments()[2];
                 message.sendToTarget();
                 return null;
             }
-        }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), any(Message.class));
+        }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), eq(false), any(Message.class));
 
         mUiccCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(mUiccProfile, null);
         processAllMessages();
@@ -766,7 +766,7 @@
         doAnswer(new Answer<Void>() {
             @Override
             public Void answer(InvocationOnMock invocation) throws Throwable {
-                Message message = (Message) invocation.getArguments()[7];
+                Message message = (Message) invocation.getArguments()[8];
                 IccIoResult iir = new IccIoResult(0x90, 0x00,
                         IccUtils.hexStringToBytes(hexString1));
                 AsyncResult ar = new AsyncResult(null, iir, null);
@@ -775,12 +775,12 @@
                 return null;
             }
         }).when(mUiccProfile).iccTransmitApduLogicalChannel(anyInt(), anyInt(), anyInt(), anyInt(),
-                eq(P2), anyInt(), anyString(), any(Message.class));
+                eq(P2), anyInt(), anyString(), eq(false), any(Message.class));
 
         doAnswer(new Answer<Void>() {
             @Override
             public Void answer(InvocationOnMock invocation) throws Throwable {
-                Message message = (Message) invocation.getArguments()[7];
+                Message message = (Message) invocation.getArguments()[8];
                 IccIoResult iir = new IccIoResult(0x90, 0x00,
                         IccUtils.hexStringToBytes(hexString2));
                 AsyncResult ar = new AsyncResult(null, iir, null);
@@ -789,16 +789,16 @@
                 return null;
             }
         }).when(mUiccProfile).iccTransmitApduLogicalChannel(anyInt(), anyInt(), anyInt(), anyInt(),
-                eq(P2_EXTENDED_DATA), anyInt(), anyString(), any(Message.class));
+                eq(P2_EXTENDED_DATA), anyInt(), anyString(), eq(false), any(Message.class));
 
         doAnswer(new Answer<Void>() {
             @Override
             public Void answer(InvocationOnMock invocation) throws Throwable {
-                Message message = (Message) invocation.getArguments()[1];
+                Message message = (Message) invocation.getArguments()[2];
                 message.sendToTarget();
                 return null;
             }
-        }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), any(Message.class));
+        }).when(mUiccProfile).iccCloseLogicalChannel(anyInt(), eq(false), any(Message.class));
 
         mUiccCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(mUiccProfile, null);
         processAllMessages();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java
index 0344e57..2ab23f3 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java
@@ -104,6 +104,8 @@
 
         doReturn(PHONE_COUNT).when(mTelephonyManager).getPhoneCount();
         doReturn(PHONE_COUNT).when(mTelephonyManager).getSimCount();
+        doReturn(IccSlotStatus.MultipleEnabledProfilesMode.NONE)
+                .when(mMockSlot).getSupportedMepMode();
         // set number of slots to 1
         mContextFixture.putIntResource(com.android.internal.R.integer.config_num_physical_slots, 1);
 
@@ -114,6 +116,8 @@
         mIccCardStatus.mCdmaSubscriptionAppIndex =
                 mIccCardStatus.mImsSubscriptionAppIndex =
                         mIccCardStatus.mGsmUmtsSubscriptionAppIndex = -1;
+        mIccCardStatus.mCardState = IccCardStatus.CardState.CARDSTATE_PRESENT;
+        mIccCardStatus.mSupportedMepMode = IccSlotStatus.MultipleEnabledProfilesMode.NONE;
         mSimulatedCommands.setIccCardStatus(mIccCardStatus);
         // for testing we pretend slotIndex is set. In reality it would be invalid on older versions
         // (before 1.2) of hal
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 bddb044..14e95f1 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccPortTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccPortTest.java
@@ -140,7 +140,7 @@
 
         record = mUiccPort.getOpenLogicalChannelRecord(CHANNEL_ID);
         assertThat(record).isNull();
-        verify(mUiccProfile).iccCloseLogicalChannel(eq(CHANNEL_ID), eq(null));
+        verify(mUiccProfile).iccCloseLogicalChannel(eq(CHANNEL_ID), eq(false), eq(null));
     }
 
     private IccLogicalChannelRequest getIccLogicalChannelRequest() {
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 4a3d4cf..a9034eb 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccProfileTest.java
@@ -23,7 +23,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.atLeast;
@@ -227,7 +227,7 @@
                 anyInt(), isA(Message.class));
         verify(mSimulatedCommandsVerifier, times(2)).iccTransmitApduLogicalChannel(
                 anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyString(),
-                isA(Message.class)
+                anyBoolean(), isA(Message.class)
         );
     }
 
@@ -577,8 +577,6 @@
 
         mUiccProfile.getApplicationIndex(0).getIccRecords().mIccId = fakeIccId;
 
-        doReturn(false).when(mSubscriptionController)
-                .checkPhoneIdAndIccIdMatch(anyInt(), anyString());
         doReturn(new SubscriptionInfoInternal.Builder().setSimSlotIndex(0).setId(1)
                 .setIccId("98765").build()).when(mSubscriptionManagerService)
                 .getSubscriptionInfoInternal(anyInt());
@@ -587,8 +585,6 @@
                 .getString("operator_branding_" + fakeIccId, null);
         assertNotEquals(fakeBrand, brandInSharedPreference);
 
-        doReturn(true).when(mSubscriptionController)
-                .checkPhoneIdAndIccIdMatch(anyInt(), anyString());
         doReturn(new SubscriptionInfoInternal.Builder().setSimSlotIndex(0).setId(1)
                 .setIccId(fakeIccId).build()).when(mSubscriptionManagerService)
                 .getSubscriptionInfoInternal(anyInt());
@@ -607,8 +603,6 @@
 
         mUiccProfile.getApplicationIndex(0).getIccRecords().mIccId = fakeIccId1;
         doReturn(fakeIccId2).when(mSubscriptionInfo).getIccId();
-        doReturn(mSubscriptionInfo).when(mSubscriptionController)
-                .getActiveSubscriptionInfoForSimSlotIndex(eq(0), any(), any());
 
         mUiccProfile.setOperatorBrandOverride(fakeBrand);
         String brandInSharedPreference = mContext.getSharedPreferences("file name", 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 6846b94..230f147 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccSlotTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccSlotTest.java
@@ -158,12 +158,7 @@
         assertTrue(mUiccSlot.isActive());
         assertNull(mUiccSlot.getUiccCard());
         assertEquals(IccCardStatus.CardState.CARDSTATE_ABSENT, mUiccSlot.getCardState());
-        if (isSubscriptionManagerServiceEnabled()) {
-            verify(mUiccController).updateSimState(phoneId, IccCardConstants.State.ABSENT, null);
-        } else {
-            verify(mSubInfoRecordUpdater).updateInternalIccState(
-                    IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, phoneId);
-        }
+        verify(mUiccController).updateSimState(phoneId, IccCardConstants.State.ABSENT, null);
     }
 
     @Test
@@ -384,12 +379,7 @@
         // Make sure when received CARDSTATE_ABSENT state in the first time,
         mIccCardStatus.mCardState = IccCardStatus.CardState.CARDSTATE_ABSENT;
         mUiccSlot.update(mSimulatedCommands, mIccCardStatus, phoneId, slotIndex);
-        if (isSubscriptionManagerServiceEnabled()) {
-            verify(mUiccController).updateSimState(phoneId, IccCardConstants.State.ABSENT, null);
-        } else {
-            verify(mSubInfoRecordUpdater).updateInternalIccState(
-                    IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, phoneId);
-        }
+        verify(mUiccController).updateSimState(phoneId, IccCardConstants.State.ABSENT, null);
         assertEquals(IccCardStatus.CardState.CARDSTATE_ABSENT, mUiccSlot.getCardState());
         assertNull(mUiccSlot.getUiccCard());
     }
@@ -422,15 +412,9 @@
         assertEquals(IccCardStatus.CardState.CARDSTATE_ABSENT, mUiccSlot.getCardState());
 
         // assert that we tried to update subscriptions
-        if (isSubscriptionManagerServiceEnabled()) {
-            verify(mUiccController).updateSimStateForInactivePort(
-                    activeIss.mSimPortInfos[0].mLogicalSlotIndex,
-                    inactiveIss.mSimPortInfos[0].mIccId);
-        } else {
-            verify(mSubInfoRecordUpdater).updateInternalIccStateForInactivePort(
-                    activeIss.mSimPortInfos[0].mLogicalSlotIndex,
-                    inactiveIss.mSimPortInfos[0].mIccId);
-        }
+        verify(mUiccController).updateSimStateForInactivePort(
+                activeIss.mSimPortInfos[0].mLogicalSlotIndex,
+                inactiveIss.mSimPortInfos[0].mIccId);
     }
 
     @Test
@@ -449,16 +433,10 @@
         assertEquals(IccCardStatus.CardState.CARDSTATE_PRESENT, mUiccSlot.getCardState());
         assertNotNull(mUiccSlot.getUiccCard());
 
-        // Simulate when SIM is removed, UiccCard and UiccProfile should be disposed and ABSENT
-        // state is sent to SubscriptionInfoUpdater.
+        // Simulate when SIM is removed
         mIccCardStatus.mCardState = IccCardStatus.CardState.CARDSTATE_ABSENT;
         mUiccSlot.update(mSimulatedCommands, mIccCardStatus, phoneId, slotIndex);
-        if (isSubscriptionManagerServiceEnabled()) {
-            verify(mUiccController).updateSimState(phoneId, IccCardConstants.State.ABSENT, null);
-        } else {
-            verify(mSubInfoRecordUpdater).updateInternalIccState(
-                    IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, phoneId);
-        }
+        verify(mUiccController).updateSimState(phoneId, IccCardConstants.State.ABSENT, null);
         verify(mUiccProfile).dispose();
         assertEquals(IccCardStatus.CardState.CARDSTATE_ABSENT, mUiccSlot.getCardState());
         assertNull(mUiccSlot.getUiccCard());
@@ -481,13 +459,7 @@
         // radio state unavailable
         mUiccSlot.onRadioStateUnavailable(phoneId);
 
-        // Verify that UNKNOWN state is sent to SubscriptionInfoUpdater in this case.
-        if (isSubscriptionManagerServiceEnabled()) {
-            verify(mUiccController).updateSimState(phoneId, IccCardConstants.State.UNKNOWN, null);
-        } else {
-            verify(mSubInfoRecordUpdater).updateInternalIccState(
-                    IccCardConstants.INTENT_VALUE_ICC_UNKNOWN, null, phoneId);
-        }
+        verify(mUiccController).updateSimState(phoneId, IccCardConstants.State.UNKNOWN, null);
         assertEquals(IccCardStatus.CardState.CARDSTATE_ABSENT, mUiccSlot.getCardState());
         assertNull(mUiccSlot.getUiccCard());
 
@@ -495,13 +467,7 @@
         mIccCardStatus.mCardState = CardState.CARDSTATE_ABSENT;
         mUiccSlot.update(mSimulatedCommands, mIccCardStatus, phoneId, slotIndex);
 
-        // Verify that ABSENT state is sent to SubscriptionInfoUpdater in this case.
-        if (isSubscriptionManagerServiceEnabled()) {
-            verify(mUiccController).updateSimState(phoneId, IccCardConstants.State.ABSENT, null);
-        } else {
-            verify(mSubInfoRecordUpdater).updateInternalIccState(
-                    IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, phoneId);
-        }
+        verify(mUiccController).updateSimState(phoneId, IccCardConstants.State.ABSENT, null);
         assertEquals(IccCardStatus.CardState.CARDSTATE_ABSENT, mUiccSlot.getCardState());
         assertNull(mUiccSlot.getUiccCard());
     }
@@ -538,4 +504,79 @@
         assertTrue("EuiccCard should be removable", mUiccSlot.isRemovable());
     }
 
+    @Test
+    @SmallTest
+    public void testMultipleEnabledProfilesData() {
+        IccSlotStatus iss = new IccSlotStatus();
+        IccSimPortInfo simPortInfo1 = new IccSimPortInfo();
+        simPortInfo1.mPortActive = false;
+        simPortInfo1.mLogicalSlotIndex = -1;
+        simPortInfo1.mIccId = "fake-iccid";
+
+        IccSimPortInfo simPortInfo2 = new IccSimPortInfo();
+        simPortInfo2.mPortActive = true;
+        simPortInfo2.mLogicalSlotIndex = 0;
+        simPortInfo2.mIccId = "fake-iccid";
+
+        iss.mSimPortInfos = new IccSimPortInfo[] {simPortInfo1, simPortInfo2};
+        iss.cardState = IccCardStatus.CardState.CARDSTATE_PRESENT;
+        iss.atr = "3B9F97C00AB1FE453FC6838031E073FE211F65D002341569810F21";
+        iss.setMultipleEnabledProfilesMode(3);
+
+
+        // initial state
+        assertEquals(IccCardStatus.CardState.CARDSTATE_ABSENT, mUiccSlot.getCardState());
+        assertEquals(IccSlotStatus.MultipleEnabledProfilesMode.NONE,
+                mUiccSlot.getSupportedMepMode());
+        assertFalse(mUiccSlot.isMultipleEnabledProfileSupported());
+
+        // update slot to inactive
+        mUiccSlot.update(null, iss, 0 /* slotIndex */);
+
+        // assert on updated values
+        assertNull(mUiccSlot.getUiccCard());
+        assertEquals(IccCardStatus.CardState.CARDSTATE_PRESENT, mUiccSlot.getCardState());
+        assertTrue(mUiccSlot.isMultipleEnabledProfileSupported());
+        assertEquals(IccSlotStatus.MultipleEnabledProfilesMode.MEP_B,
+                mUiccSlot.getSupportedMepMode());
+
+        iss.mSimPortInfos = new IccSimPortInfo[] {simPortInfo1};
+        iss.setMultipleEnabledProfilesMode(1); // Set MEP mode to MEP-A1
+
+        // update port info and MEP mode
+        mUiccSlot.update(null, iss, 0 /* slotIndex */);
+
+        // assert on updated values
+        assertTrue(mUiccSlot.isMultipleEnabledProfileSupported());
+        assertEquals(IccSlotStatus.MultipleEnabledProfilesMode.MEP_A1,
+                mUiccSlot.getSupportedMepMode());
+
+        //update port info and MEP mode to test HAL version 2.0
+        iss.mSimPortInfos = new IccSimPortInfo[] {simPortInfo1, simPortInfo2};
+        iss.setMultipleEnabledProfilesMode(0); // Set MEP mode to NONE(assume modem sends)
+
+        // update port info and MEP mode
+        mUiccSlot.update(null, iss, 0 /* slotIndex */);
+        assertTrue(mUiccSlot.isMultipleEnabledProfileSupported());
+        assertEquals(IccSlotStatus.MultipleEnabledProfilesMode.MEP_B,
+                mUiccSlot.getSupportedMepMode());
+    }
+
+    @Test
+    @SmallTest
+    public void testSimStateUnknown() {
+        int phoneId = 0;
+        int slotIndex = 0;
+        // Initially state is unknown
+        assertTrue(mUiccSlot.isStateUnknown());
+        mIccCardStatus.mCardState = IccCardStatus.CardState.CARDSTATE_ABSENT;
+        mUiccSlot.update(mSimulatedCommands, mIccCardStatus, phoneId, slotIndex);
+        assertNull(mUiccSlot.getUiccCard());
+        // As CardState is absent, state should not be unknown
+        assertFalse(mUiccSlot.isStateUnknown());
+        // radio state unavailable
+        mUiccSlot.onRadioStateUnavailable(phoneId);
+        // When radio is not available, state is unknown
+        assertTrue(mUiccSlot.isStateUnknown());
+    }
 }
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 18247d3..7e51bad 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccStateChangedLauncherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccStateChangedLauncherTest.java
@@ -99,7 +99,8 @@
 
         // The first broadcast should be sent after initialization.
         UiccCard card = new UiccCard(mContext, mSimulatedCommands,
-                makeCardStatus(CardState.CARDSTATE_PRESENT), 0 /* phoneId */, new Object(), false);
+                makeCardStatus(CardState.CARDSTATE_PRESENT), 0 /* phoneId */, new Object(),
+                IccSlotStatus.MultipleEnabledProfilesMode.NONE);
         when(UiccController.getInstance().getUiccCardForPhone(0)).thenReturn(card);
         uiccLauncher.handleMessage(msg);
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java
index 79c4af4..b6dd7bd 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
@@ -34,6 +35,7 @@
 import com.android.internal.telephony.uicc.IccCardApplicationStatus;
 import com.android.internal.telephony.uicc.IccCardStatus;
 import com.android.internal.telephony.uicc.IccSlotPortMapping;
+import com.android.internal.telephony.uicc.IccSlotStatus;
 import com.android.internal.telephony.uicc.euicc.apdu.LogicalChannelMocker;
 import com.android.internal.telephony.uicc.euicc.async.AsyncResultCallback;
 
@@ -94,7 +96,7 @@
 
         mEuiccCard =
             new EuiccCard(mContext, mMockCi, mMockIccCardStatus,
-                0 /* phoneId */, new Object(), false) {
+                0 /* phoneId */, new Object(), IccSlotStatus.MultipleEnabledProfilesMode.NONE) {
 
                 @Override
                 protected void loadEidAndNotifyRegistrants() {}
@@ -133,7 +135,8 @@
     public void testPassEidInConstructor() {
         mMockIccCardStatus.eid = "1A2B3C4D";
         mEuiccCard = new EuiccCard(mContextFixture.getTestDouble(), mMockCi,
-                mMockIccCardStatus, 0 /* phoneId */, new Object(), false);
+                mMockIccCardStatus, 0 /* phoneId */, new Object(),
+                IccSlotStatus.MultipleEnabledProfilesMode.NONE);
 
         final int eventEidReady = 0;
         Handler handler = new Handler(Looper.myLooper()) {
@@ -154,7 +157,8 @@
         int channel = mockLogicalChannelResponses("BF3E065A041A2B3C4D9000");
         mHandler.post(() -> {
             mEuiccCard = new EuiccCard(mContextFixture.getTestDouble(), mMockCi,
-                    mMockIccCardStatus, 0 /* phoneId */, new Object(), false);
+                    mMockIccCardStatus, 0 /* phoneId */, new Object(),
+                    IccSlotStatus.MultipleEnabledProfilesMode.NONE);
         });
         processAllMessages();
 
@@ -188,7 +192,7 @@
     private void verifyStoreData(int channel, String command) {
         verify(mMockCi, times(1))
                 .iccTransmitApduLogicalChannel(eq(channel), eq(0x80 | channel), eq(0xE2), eq(0x91),
-                        eq(0), eq(command.length() / 2), eq(command), any());
+                        eq(0), eq(command.length() / 2), eq(command), anyBoolean(), any());
     }
 
     private int mockLogicalChannelResponses(Object... responses) {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccPortTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccPortTest.java
index 90163cf..d140ca8 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccPortTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccPortTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
@@ -49,6 +50,8 @@
 import com.android.internal.telephony.uicc.IccCardApplicationStatus;
 import com.android.internal.telephony.uicc.IccCardStatus;
 import com.android.internal.telephony.uicc.IccSlotPortMapping;
+import com.android.internal.telephony.uicc.IccSlotStatus;
+import com.android.internal.telephony.uicc.IccSlotStatus.MultipleEnabledProfilesMode;
 import com.android.internal.telephony.uicc.IccUtils;
 import com.android.internal.telephony.uicc.asn1.Asn1Node;
 import com.android.internal.telephony.uicc.asn1.InvalidAsn1DataException;
@@ -118,7 +121,8 @@
         mMockIccCardStatus.mSlotPortMapping = new IccSlotPortMapping();
         mEuiccPort =
             new EuiccPort(mContext, mMockCi, mMockIccCardStatus,
-                0 /* phoneId */, new Object(), mEuiccCard, false) {
+                0 /* phoneId */, new Object(), mEuiccCard,
+                    IccSlotStatus.MultipleEnabledProfilesMode.NONE) {
                 @Override
                 protected byte[] getDeviceId() {
                     return IccUtils.bcdToBytes("987654321012345");
@@ -171,7 +175,7 @@
                 "BF2D14A012E3105A0A896700000000004523019F7001019000");
 
         ResultCaptor<EuiccProfileInfo[]> resultCaptor = new ResultCaptor<>();
-        mEuiccPort.mIsSupportsMultipleEnabledProfiles = true; // MEP capable
+        mEuiccPort.mSupportedMepMode = MultipleEnabledProfilesMode.MEP_B; // MEP capable
         mEuiccPort.getAllProfiles(resultCaptor, mHandler);
         processAllMessages();
 
@@ -180,7 +184,7 @@
         assertEquals(1, profiles.length);
         assertEquals("98760000000000543210", profiles[0].getIccid());
         assertEquals(EuiccProfileInfo.PROFILE_STATE_ENABLED, profiles[0].getState());
-        verifyStoreData(channel, "BF2D0F5C0D5A909192B79F709599BF769F20");
+        verifyStoreData(channel, "BF2D0F5C0D5A909192B79F709599BF769F24");
     }
 
     @Test
@@ -203,10 +207,10 @@
     @Test
     public void testEnabledOnEsimPort_GetAllProfiles() {
         int channel = mockLogicalChannelResponses(
-                "BF2D18A016E3145A0A896700000000004523019F7001009F2001019000");
+                "BF2D18A016E3145A0A896700000000004523019F7001009F2401019000");
 
         ResultCaptor<EuiccProfileInfo[]> resultCaptor = new ResultCaptor<>();
-        mEuiccPort.mIsSupportsMultipleEnabledProfiles = true; // MEP capable
+        mEuiccPort.mSupportedMepMode = MultipleEnabledProfilesMode.MEP_B; // MEP capable
         mEuiccPort.getAllProfiles(resultCaptor, mHandler);
         processAllMessages();
 
@@ -218,7 +222,7 @@
         // which is valid port. So the state should be enabled.
         // (As per MEP state and enabledOnEsimPort concept)
         assertEquals(EuiccProfileInfo.PROFILE_STATE_ENABLED, profiles[0].getState());
-        verifyStoreData(channel, "BF2D0F5C0D5A909192B79F709599BF769F20");
+        verifyStoreData(channel, "BF2D0F5C0D5A909192B79F709599BF769F24");
     }
 
     @Test
@@ -228,14 +232,14 @@
                 "BF2D14A012E3105A0A896700000000004523FF9F7001009000");
 
         ResultCaptor<EuiccProfileInfo[]> resultCaptor = new ResultCaptor<>();
-        mEuiccPort.mIsSupportsMultipleEnabledProfiles = true; // MEP capable
+        mEuiccPort.mSupportedMepMode = MultipleEnabledProfilesMode.MEP_B; // MEP capable
         mEuiccPort.getAllProfiles(resultCaptor, mHandler);
         processAllMessages();
 
         EuiccProfileInfo[] profiles = resultCaptor.result;
         assertEquals(1, profiles.length);
         assertEquals(EuiccProfileInfo.PROFILE_STATE_DISABLED, profiles[0].getState());
-        verifyStoreData(channel, "BF2D0F5C0D5A909192B79F709599BF769F20");
+        verifyStoreData(channel, "BF2D0F5C0D5A909192B79F709599BF769F24");
     }
 
     @Test
@@ -375,6 +379,22 @@
     }
 
     @Test
+    public void testSwitchToProfile_MepA1() {
+        int channel = mockLogicalChannelResponses("BF31038001039000");
+
+        ResultCaptor<Void> resultCaptor = new ResultCaptor<>();
+        mMockIccCardStatus.mSlotPortMapping.mPortIndex = 1;
+        mEuiccPort.updateSupportedMepMode(MultipleEnabledProfilesMode.MEP_A1);
+        mEuiccPort.update(mContext, mMockCi, mMockIccCardStatus, mEuiccCard);
+        mEuiccPort.switchToProfile("98760000000000543210", true, resultCaptor, mHandler);
+        processAllMessages();
+
+        assertEquals(3, ((EuiccCardErrorException) resultCaptor.exception).getErrorCode());
+        // In case of MEP-A1, verify portIndex is shifted or not.
+        verifyStoreData(channel, "BF3114A00C5A0A896700000000004523018101FF820102");
+    }
+
+    @Test
     public void testGetEid() {
         int channel = mockLogicalChannelResponses("BF3E065A041A2B3C4D9000");
 
@@ -884,7 +904,7 @@
         verify(mMockCi, never())
                 .iccTransmitApduLogicalChannel(
                         eq(channel), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(),
-                        any());
+                        anyBoolean(), any());
     }
 
     @Test
@@ -917,7 +937,7 @@
         verify(mMockCi, never())
                 .iccTransmitApduLogicalChannel(
                         eq(channel), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(),
-                        any());
+                        anyBoolean(), any());
     }
 
     @Test
@@ -1175,7 +1195,7 @@
     private void verifyStoreData(int channel, String command) {
         verify(mMockCi, times(1))
                 .iccTransmitApduLogicalChannel(eq(channel), eq(0x80 | channel), eq(0xE2), eq(0x91),
-                        eq(0), eq(command.length() / 2), eq(command), any());
+                        eq(0), eq(command.length() / 2), eq(command), anyBoolean(), any());
     }
 
     private int mockLogicalChannelResponses(Object... responses) {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/ApduSenderTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/ApduSenderTest.java
index 2b9e767..b073c6a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/ApduSenderTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/ApduSenderTest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
@@ -119,7 +120,7 @@
         assertNull(mResponseCaptor.response);
         assertNull(mResponseCaptor.exception);
         verify(mMockCi).iccOpenLogicalChannel(eq(AID), anyInt(), any());
-        verify(mMockCi).iccCloseLogicalChannel(eq(channel), any());
+        verify(mMockCi).iccCloseLogicalChannel(eq(channel), eq(true /*isEs10*/), any());
     }
 
     @Test
@@ -149,7 +150,7 @@
 
         assertEquals("A1A1A1", IccUtils.bytesToHexString(mResponseCaptor.response));
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2),
-                eq(3), eq(0), eq("a"), any());
+                eq(3), eq(0), eq("a"), anyBoolean(), any());
     }
 
     @Test
@@ -169,13 +170,13 @@
 
         assertEquals("A4", IccUtils.bytesToHexString(mResponseCaptor.response));
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2),
-                eq(3), eq(0), eq("a"), any());
+                eq(3), eq(0), eq("a"), anyBoolean(), any());
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2),
-                eq(3), eq(1), eq("ab"), any());
+                eq(3), eq(1), eq("ab"), anyBoolean(), any());
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2),
-                eq(3), eq(0), eq(""), any());
+                eq(3), eq(0), eq(""), anyBoolean(), any());
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x91),
-                eq(0), eq(2), eq("abcd"), any());
+                eq(0), eq(2), eq("abcd"), anyBoolean(), any());
     }
 
     @Test
@@ -196,11 +197,11 @@
 
         assertEquals("A3", IccUtils.bytesToHexString(mResponseCaptor.response));
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2),
-                eq(3), eq(0), eq("a"), any());
+                eq(3), eq(0), eq("a"), anyBoolean(), any());
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2),
-                eq(3), eq(1), eq("ab"), any());
+                eq(3), eq(1), eq("ab"), anyBoolean(), any());
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2),
-                eq(3), eq(0), eq(""), any());
+                eq(3), eq(0), eq(""), anyBoolean(), any());
     }
 
     @Test
@@ -216,11 +217,11 @@
 
         assertEquals("A1A1A1B2B2B2B2C3C3", IccUtils.bytesToHexString(mResponseCaptor.response));
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2),
-                eq(3), eq(0), eq("a"), any());
+                eq(3), eq(0), eq("a"), anyBoolean(), any());
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel), eq(0xC0), eq(0),
-                eq(0), eq(4), eq(""), any());
+                eq(0), eq(4), eq(""), anyBoolean(), any());
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel), eq(0xC0), eq(0),
-                eq(0), eq(2), eq(""), any());
+                eq(0), eq(2), eq(""), anyBoolean(), any());
     }
 
     @Test
@@ -244,15 +245,15 @@
 
         assertEquals("C3", IccUtils.bytesToHexString(mResponseCaptor.response));
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2),
-                eq(3), eq(0), eq("a"), any());
+                eq(3), eq(0), eq("a"), anyBoolean(), any());
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2),
-                eq(3), eq(0), eq("b"), any());
+                eq(3), eq(0), eq("b"), anyBoolean(), any());
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x11),
-                eq(0), eq(0xFF), eq(s1), any());
+                eq(0), eq(0xFF), eq(s1), anyBoolean(), any());
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x11),
-                eq(1), eq(0xFF), eq(s2), any());
+                eq(1), eq(0xFF), eq(s2), anyBoolean(), any());
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x91),
-                eq(2), eq(16), eq(s3), any());
+                eq(2), eq(16), eq(s3), anyBoolean(), any());
     }
 
     @Test
@@ -272,9 +273,9 @@
 
         assertEquals("B2222B", IccUtils.bytesToHexString(mResponseCaptor.response));
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x11),
-                eq(0), eq(0xFF), eq(s1), any());
+                eq(0), eq(0xFF), eq(s1), anyBoolean(), any());
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x91),
-                eq(1), eq(0xFF), eq(s2), any());
+                eq(1), eq(0xFF), eq(s2), anyBoolean(), any());
     }
 
     @Test
@@ -290,7 +291,7 @@
 
         assertEquals("B2222B", IccUtils.bytesToHexString(mResponseCaptor.response));
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x91),
-                eq(0), eq(0), eq(""), any());
+                eq(0), eq(0), eq(""), anyBoolean(), any());
     }
 
     @Test
@@ -313,13 +314,13 @@
 
         assertEquals(0x6985, ((ApduException) mResponseCaptor.exception).getApduStatus());
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2),
-                eq(3), eq(0), eq("a"), any());
+                eq(3), eq(0), eq("a"), anyBoolean(), any());
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x11),
-                eq(0), eq(0xFF), eq(s1), any());
+                eq(0), eq(0xFF), eq(s1), anyBoolean(), any());
         verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x11),
-                eq(1), eq(0xFF), eq(s2), any());
+                eq(1), eq(0xFF), eq(s2), anyBoolean(), any());
         verify(mMockCi, never()).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2),
-                eq(0x91), eq(2), eq(16), eq(s3), any());
+                eq(0x91), eq(2), eq(16), eq(s3), anyBoolean(), any());
     }
 
     @Test
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/LogicalChannelMocker.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/LogicalChannelMocker.java
index e9796a1..27f743f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/LogicalChannelMocker.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/LogicalChannelMocker.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony.uicc.euicc.apdu;
 
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
@@ -64,32 +65,39 @@
     public static void mockSendToLogicalChannel(CommandsInterface mockCi, int channel,
             Object... responseObjects) {
         ArgumentCaptor<Message> response = ArgumentCaptor.forClass(Message.class);
+
         doAnswer(new Answer() {
             private int mIndex = 0;
 
             @Override
             public Object answer(InvocationOnMock invocation) throws Throwable {
                 Object responseObject = responseObjects[mIndex++];
-                boolean isException = responseObject instanceof Throwable;
-                int sw1 = 0;
-                int sw2 = 0;
-                String hex = responseObject.toString();
-                if (!isException) {
-                    int l = hex.length();
-                    sw1 = Integer.parseInt(hex.substring(l - 4, l - 2), 16);
-                    sw2 = Integer.parseInt(hex.substring(l - 2), 16);
-                    hex = hex.substring(0, l - 4);
-                }
-                IccIoResult result = isException ? null : new IccIoResult(sw1, sw2, hex);
-                Throwable exception = isException ? (Throwable) responseObject : null;
-
-                Message msg = response.getValue();
-                AsyncResult.forMessage(msg, result, exception);
-                msg.sendToTarget();
+                mockIccTransmitApduLogicalChannelResponse(response, responseObject);
                 return null;
             }
         }).when(mockCi).iccTransmitApduLogicalChannel(eq(channel), anyInt(), anyInt(), anyInt(),
-                anyInt(), anyInt(), anyString(), response.capture());
+                anyInt(), anyInt(), anyString(), anyBoolean(), response.capture());
+    }
+
+    private static void mockIccTransmitApduLogicalChannelResponse(ArgumentCaptor<Message> response,
+            Object responseObject) throws Throwable {
+
+        boolean isException = responseObject instanceof Throwable;
+        int sw1 = 0;
+        int sw2 = 0;
+        String hex = responseObject.toString();
+        if (!isException) {
+            int l = hex.length();
+            sw1 = Integer.parseInt(hex.substring(l - 4, l - 2), 16);
+            sw2 = Integer.parseInt(hex.substring(l - 2), 16);
+            hex = hex.substring(0, l - 4);
+        }
+        IccIoResult result = isException ? null : new IccIoResult(sw1, sw2, hex);
+        Throwable exception = isException ? (Throwable) responseObject : null;
+
+        Message msg = response.getValue();
+        AsyncResult.forMessage(msg, result, exception);
+        msg.sendToTarget();
     }
 
     public static void mockCloseLogicalChannel(CommandsInterface mockCi, int channel) {
@@ -99,7 +107,8 @@
             AsyncResult.forMessage(msg);
             msg.sendToTarget();
             return null;
-        }).when(mockCi).iccCloseLogicalChannel(eq(channel), response.capture());
+        }).when(mockCi).iccCloseLogicalChannel(eq(channel),
+                eq(true /*isEs10*/), response.capture());
     }
 
     private static int[] getSelectResponse(String responseHex) {