Snap for 10453563 from 94ec5a14cea628292aa957a95d5fa325e015072f to mainline-adservices-release

Change-Id: If3da7428500593bed6bb26f5bbc78998d67b3dd9
diff --git a/Android.bp b/Android.bp
index fab53d1..4097571 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 94509fe..a061cf0 100644
--- a/OWNERS
+++ b/OWNERS
@@ -8,15 +8,15 @@
 jackyu@google.com
 jayachandranc@google.com
 linggm@google.com
-mtelang@google.com
+pmadapurmath@google.com
 rgreenwalt@google.com
 sarahchin@google.com
 sasindran@google.com
 tgunn@google.com
 tjstuart@google.com
+tnd@google.com
 xiaotonj@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 c10eeb5..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: 50
+// Next id: 70
 message PersistAtoms {
     /* Aggregated RAT usage during the call. */
     repeated VoiceCallRatUsage voice_call_rat_usage = 1;
@@ -177,6 +177,60 @@
 
     /* Timestamp of last network_requests_v2 pull. */
     optional int64 network_requests_v2_pull_timestamp_millis = 51;
+
+    /* 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:
@@ -187,10 +241,10 @@
 // NOTE: StatsLog functions use int in place of enum
 
 message VoiceCallSession {
+    reserved 4;
     optional int32 bearer_at_start = 1;
     optional int32 bearer_at_end = 2;
     optional int32 direction = 3;
-    optional int32 setup_duration = 4;
     optional bool setup_failed = 5;
     optional int32 disconnect_reason_code = 6;
     optional int32 disconnect_extra_code = 7;
@@ -219,6 +273,9 @@
     optional int32 rat_at_connected = 30;
     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;
 }
@@ -245,6 +302,11 @@
     optional bool is_esim = 12;
     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;
 }
 
 message OutgoingSms {
@@ -262,6 +324,13 @@
     optional int64 message_id = 12;
     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;
 }
 
 message CarrierIdMismatch {
@@ -292,6 +361,8 @@
     optional bool ongoing = 18;
     optional int32 band_at_end = 19;
     repeated int32 handover_failure_causes = 20;
+    repeated int32 handover_failure_rat = 21;
+    optional bool is_non_dds = 22;
 }
 
 message CellularServiceState {
@@ -305,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;
@@ -498,3 +571,104 @@
     optional int32 failed_reason = 4;
     optional int32 count = 5;
 }
+
+message UnmeteredNetworks {
+    optional int32 phone_id = 1;
+    optional int32 carrier_id = 2;
+    optional int64 unmetered_networks_bitmask = 3;
+}
+
+message OutgoingShortCodeSms {
+    optional int32 category = 1;
+    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/proto/src/telephony.proto b/proto/src/telephony.proto
index 6d3d711..b87728b 100644
--- a/proto/src/telephony.proto
+++ b/proto/src/telephony.proto
@@ -931,6 +931,9 @@
 
       /** Data switch caused by CBRS switch. */
       DATA_SWITCH_REASON_CBRS = 3;
+
+      /** Data switch caused by non-default SIM having better availability(e.g.signal). */
+      DATA_SWITCH_REASON_AUTO = 4;
     }
 
     /** The reason for data switch. */
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/CallStateException.java b/src/java/com/android/internal/telephony/CallStateException.java
index 3fdc444..4de16cb 100644
--- a/src/java/com/android/internal/telephony/CallStateException.java
+++ b/src/java/com/android/internal/telephony/CallStateException.java
@@ -35,6 +35,7 @@
     public static final int ERROR_CALLING_DISABLED = 5;
     public static final int ERROR_TOO_MANY_CALLS = 6;
     public static final int ERROR_OTASP_PROVISIONING_IN_PROCESS = 7;
+    public static final int ERROR_FDN_BLOCKED = 8;
 
     public
     CallStateException()
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/CarrierActionAgent.java b/src/java/com/android/internal/telephony/CarrierActionAgent.java
index 42a4f23..6d74c18 100644
--- a/src/java/com/android/internal/telephony/CarrierActionAgent.java
+++ b/src/java/com/android/internal/telephony/CarrierActionAgent.java
@@ -27,6 +27,7 @@
 import android.os.RegistrantList;
 import android.provider.Settings;
 import android.provider.Telephony;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.LocalLog;
 import android.util.Log;
@@ -93,7 +94,11 @@
                     // ignore rebroadcast since carrier apps are direct boot aware.
                     return;
                 }
-                sendMessage(obtainMessage(EVENT_SIM_STATE_CHANGED, iccState));
+                final int phoneId = intent.getIntExtra(PhoneConstants.PHONE_KEY,
+                        SubscriptionManager.INVALID_PHONE_INDEX);
+                if (mPhone.getPhoneId() == phoneId) {
+                    sendMessage(obtainMessage(EVENT_SIM_STATE_CHANGED, iccState));
+                }
             }
         }
     };
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/CarrierKeyDownloadManager.java b/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
index edf3d5f..beb6b26 100644
--- a/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
+++ b/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
@@ -16,8 +16,6 @@
 
 package com.android.internal.telephony;
 
-import static android.telephony.CarrierConfigManager.KEY_ALLOW_METERED_NETWORK_FOR_CERT_DOWNLOAD_BOOL;
-
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import android.app.AlarmManager;
@@ -32,7 +30,6 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.PersistableBundle;
-import android.provider.Telephony;
 import android.telephony.CarrierConfigManager;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.SubscriptionManager;
@@ -122,13 +119,22 @@
         mPhone = phone;
         mContext = phone.getContext();
         IntentFilter filter = new IntentFilter();
-        filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
         filter.addAction(INTENT_KEY_RENEWAL_ALARM_PREFIX);
         filter.addAction(TelephonyIntents.ACTION_CARRIER_CERTIFICATE_DOWNLOAD);
         mContext.registerReceiver(mBroadcastReceiver, filter, null, phone);
         mDownloadManager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
         mTelephonyManager = mContext.getSystemService(TelephonyManager.class)
                 .createForSubscriptionId(mPhone.getSubId());
+        CarrierConfigManager carrierConfigManager = mContext.getSystemService(
+                CarrierConfigManager.class);
+        // Callback which directly handle config change should be executed on handler thread
+        carrierConfigManager.registerCarrierConfigChangeListener(this::post,
+                (slotIndex, subId, carrierId, specificCarrierId) -> {
+                    if (slotIndex == mPhone.getPhoneId()) {
+                        Log.d(LOG_TAG, "Carrier Config changed: slotIndex=" + slotIndex);
+                        handleAlarmOrConfigChange();
+                    }
+                });
     }
 
     private final BroadcastReceiver mDownloadReceiver = new BroadcastReceiver() {
@@ -161,12 +167,6 @@
                     Log.d(LOG_TAG, "Handling reset intent: " + action);
                     sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE);
                 }
-            } else if (action.equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
-                if (phoneId == intent.getIntExtra(PhoneConstants.PHONE_KEY,
-                        SubscriptionManager.INVALID_SIM_SLOT_INDEX)) {
-                    Log.d(LOG_TAG, "Carrier Config changed: " + action);
-                    sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE);
-                }
             }
         }
     };
@@ -389,14 +389,23 @@
             return false;
         }
         int subId = mPhone.getSubId();
-        PersistableBundle b = carrierConfigManager.getConfigForSubId(subId);
-        if (b == null) {
+        PersistableBundle b = null;
+        try {
+            b = carrierConfigManager.getConfigForSubId(subId,
+                    CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT,
+                    CarrierConfigManager.IMSI_KEY_DOWNLOAD_URL_STRING,
+                    CarrierConfigManager.KEY_ALLOW_METERED_NETWORK_FOR_CERT_DOWNLOAD_BOOL);
+        } catch (RuntimeException e) {
+            Log.e(LOG_TAG, "CarrierConfigLoader is not available.");
+        }
+        if (b == null || b.isEmpty()) {
             return false;
         }
+
         mKeyAvailability = b.getInt(CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT);
         mURL = b.getString(CarrierConfigManager.IMSI_KEY_DOWNLOAD_URL_STRING);
         mAllowedOverMeteredNetwork = b.getBoolean(
-                KEY_ALLOW_METERED_NETWORK_FOR_CERT_DOWNLOAD_BOOL);
+                CarrierConfigManager.KEY_ALLOW_METERED_NETWORK_FOR_CERT_DOWNLOAD_BOOL);
         if (mKeyAvailability == 0 || TextUtils.isEmpty(mURL)) {
             Log.d(LOG_TAG,
                     "Carrier not enabled or invalid values. mKeyAvailability=" + mKeyAvailability
diff --git a/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java b/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java
index 1e184bb..ab7ebc4 100644
--- a/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java
+++ b/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java
@@ -16,11 +16,8 @@
 
 package com.android.internal.telephony;
 
-import static android.telephony.CarrierConfigManager.EXTRA_SLOT_INDEX;
-import static android.telephony.CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX;
 import static android.telephony.CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY;
 import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX;
-import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
 import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
 import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED;
@@ -31,8 +28,6 @@
 import static android.telephony.TelephonyManager.SIM_STATE_READY;
 import static android.telephony.TelephonyManager.SIM_STATE_UNKNOWN;
 
-import static com.android.internal.telephony.SubscriptionInfoUpdater.simStateString;
-
 import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -54,8 +49,6 @@
 import android.os.Message;
 import android.os.PersistableBundle;
 import android.os.Process;
-import android.os.Registrant;
-import android.os.RegistrantList;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -69,7 +62,6 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.IntArray;
 import android.util.LocalLog;
 import android.util.Pair;
 
@@ -86,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;
@@ -111,6 +104,7 @@
     private static final int PACKAGE_NOT_PRIVILEGED = 0;
     private static final int PACKAGE_PRIVILEGED_FROM_CARRIER_CONFIG = 1;
     private static final int PACKAGE_PRIVILEGED_FROM_SIM = 2;
+    private static final int PACKAGE_PRIVILEGED_FROM_CARRIER_SERVICE_TEST_OVERRIDE = 3;
 
     // TODO(b/232273884): Turn feature on when find solution to handle the inter-carriers switching
     /**
@@ -133,25 +127,6 @@
                     | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS;
 
     /**
-     * Action to register a Registrant with this Tracker.
-     * obj: Registrant that will be notified of Carrier Privileged UID changes.
-     */
-    private static final int ACTION_REGISTER_LISTENER = 1;
-
-    /**
-     * Action to unregister a Registrant with this Tracker.
-     * obj: Handler used by the Registrant that will be removed.
-     */
-    private static final int ACTION_UNREGISTER_LISTENER = 2;
-
-    /**
-     * Action for tracking when Carrier Configs are updated.
-     * arg1: Subscription Id for the Carrier Configs update being broadcast
-     * arg2: Slot Index for the Carrier Configs update being broadcast
-     */
-    private static final int ACTION_CARRIER_CONFIG_CERTS_UPDATED = 3;
-
-    /**
      * Action for tracking when the Phone's SIM state changes.
      * arg1: slotId that this Action applies to
      * arg2: simState reported by this Broadcast
@@ -192,6 +167,14 @@
      */
     private static final int ACTION_UICC_ACCESS_RULES_LOADED = 10;
 
+    /**
+     * Action to set the test override rule through {@link
+     * TelephonyManager#setCarrierServicePackageOverride}.
+     *
+     * <p>obj: String of the carrierServicePackage from method setCarrierServicePackageOverride.
+     */
+    private static final int ACTION_SET_TEST_OVERRIDE_CARRIER_SERVICE_PACKAGE = 11;
+
     private final Context mContext;
     private final Phone mPhone;
     private final PackageManager mPackageManager;
@@ -201,7 +184,6 @@
     private final TelephonyRegistryManager mTelephonyRegistryManager;
 
     @NonNull private final LocalLog mLocalLog = new LocalLog(64);
-    @NonNull private final RegistrantList mRegistrantList = new RegistrantList();
     // Stores rules for Carrier Config-loaded rules
     @NonNull private final List<UiccAccessRule> mCarrierConfigRules = new ArrayList<>();
     // Stores rules for SIM-loaded rules.
@@ -211,6 +193,7 @@
     // - Empty list indicates test override to simulate no rules (CC and UICC rules are ignored)
     // - Non-empty list indicates test override with specific rules (CC and UICC rules are ignored)
     @Nullable private List<UiccAccessRule> mTestOverrideRules = null;
+    @Nullable private String mTestOverrideCarrierServicePackage = null;
     // Map of PackageName -> Certificate hashes for that Package
     @NonNull private final Map<String, Set<String>> mInstalledPackageCerts = new ArrayMap<>();
     // Map of PackageName -> UIDs for that Package
@@ -300,19 +283,6 @@
                     if (action == null) return;
 
                     switch (action) {
-                        case CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED: {
-                            Bundle extras = intent.getExtras();
-                            int slotIndex = extras.getInt(EXTRA_SLOT_INDEX);
-                            int subId =
-                                    extras.getInt(
-                                            EXTRA_SUBSCRIPTION_INDEX, INVALID_SUBSCRIPTION_ID);
-                            sendMessage(
-                                    obtainMessage(
-                                            ACTION_CARRIER_CONFIG_CERTS_UPDATED,
-                                            subId,
-                                            slotIndex));
-                            break;
-                        }
                         case TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED: // fall through
                         case TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED: {
                             Bundle extras = intent.getExtras();
@@ -375,13 +345,16 @@
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         mCarrierConfigManager =
                 (CarrierConfigManager) mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        // Callback is executed in handler thread and directly handles carrier config update
+        mCarrierConfigManager.registerCarrierConfigChangeListener(this::post,
+                (slotIndex, subId, carrierId, specificCarrierId) -> handleCarrierConfigUpdated(
+                        subId, slotIndex));
         mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
         mTelephonyRegistryManager =
                 (TelephonyRegistryManager)
                         mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
 
         IntentFilter certFilter = new IntentFilter();
-        certFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
         certFilter.addAction(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED);
         certFilter.addAction(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED);
         mContext.registerReceiver(mIntentReceiver, certFilter);
@@ -403,20 +376,6 @@
     @Override
     public void handleMessage(Message msg) {
         switch (msg.what) {
-            case ACTION_REGISTER_LISTENER: {
-                handleRegisterListener((Registrant) msg.obj);
-                break;
-            }
-            case ACTION_UNREGISTER_LISTENER: {
-                handleUnregisterListener((Handler) msg.obj);
-                break;
-            }
-            case ACTION_CARRIER_CONFIG_CERTS_UPDATED: {
-                int subId = msg.arg1;
-                int slotIndex = msg.arg2;
-                handleCarrierConfigUpdated(subId, slotIndex);
-                break;
-            }
             case ACTION_SIM_STATE_UPDATED: {
                 handleSimStateChanged(msg.arg1, msg.arg2);
                 break;
@@ -448,6 +407,11 @@
                 handleUiccAccessRulesLoaded();
                 break;
             }
+            case ACTION_SET_TEST_OVERRIDE_CARRIER_SERVICE_PACKAGE: {
+                String carrierServicePackage = (String) msg.obj;
+                handleSetTestOverrideCarrierServicePackage(carrierServicePackage);
+                break;
+            }
             default: {
                 Rlog.e(TAG, "Received unknown msg type: " + msg.what);
                 break;
@@ -455,23 +419,6 @@
         }
     }
 
-    private void handleRegisterListener(@NonNull Registrant registrant) {
-        mRegistrantList.add(registrant);
-        mPrivilegedPackageInfoLock.readLock().lock();
-        try {
-            // Old registrant callback still takes int[] as parameter, need conversion here
-            int[] uids = intSetToArray(mPrivilegedPackageInfo.mUids);
-            registrant.notifyResult(
-                    Arrays.copyOf(uids, uids.length));
-        } finally {
-            mPrivilegedPackageInfoLock.readLock().unlock();
-        }
-    }
-
-    private void handleUnregisterListener(@NonNull Handler handler) {
-        mRegistrantList.remove(handler);
-    }
-
     private void handleCarrierConfigUpdated(int subId, int slotIndex) {
         if (slotIndex != mPhone.getPhoneId()) return;
 
@@ -493,7 +440,10 @@
 
     @NonNull
     private List<UiccAccessRule> getCarrierConfigRules(int subId) {
-        PersistableBundle carrierConfigs = mCarrierConfigManager.getConfigForSubId(subId);
+        PersistableBundle carrierConfigs =
+                CarrierConfigManager.getCarrierConfigSubset(
+                        mContext, subId, KEY_CARRIER_CERTIFICATE_STRING_ARRAY);
+        // CarrierConfigManager#isConfigForIdentifiedCarrier can handle null or empty bundle
         if (!mCarrierConfigManager.isConfigForIdentifiedCarrier(carrierConfigs)) {
             return Collections.EMPTY_LIST;
         }
@@ -529,7 +479,7 @@
                         SystemClock.uptimeMillis() + CLEAR_UICC_RULES_DELAY_MILLIS;
                 sendMessageAtTime(obtainMessage(ACTION_CLEAR_UICC_RULES),
                         mClearUiccRulesUptimeMillis);
-                mLocalLog.log("SIM is gone, simState=" + simStateString(simState)
+                mLocalLog.log("SIM is gone, simState=" + TelephonyManager.simStateToString(simState)
                         + ". Delay " + TimeUnit.MILLISECONDS.toSeconds(
                         CLEAR_UICC_RULES_DELAY_MILLIS) + " seconds to clear UICC rules.");
             } else {
@@ -614,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);
@@ -731,12 +681,6 @@
 
         mPrivilegedPackageInfoLock.readLock().lock();
         try {
-            // The obsoleted callback only care about UIDs
-            if (carrierPrivilegesUidsChanged) {
-                int[] uids = intSetToArray(mPrivilegedPackageInfo.mUids);
-                mRegistrantList.notifyResult(Arrays.copyOf(uids, uids.length));
-            }
-
             if (carrierPrivilegesPackageNamesChanged || carrierPrivilegesUidsChanged) {
                 mTelephonyRegistryManager.notifyCarrierPrivilegesChanged(
                         mPhone.getPhoneId(),
@@ -768,6 +712,7 @@
             final int priv = getPackagePrivilegedStatus(e.getKey(), e.getValue());
             switch (priv) {
                 case PACKAGE_PRIVILEGED_FROM_SIM:
+                case PACKAGE_PRIVILEGED_FROM_CARRIER_SERVICE_TEST_OVERRIDE: // fallthrough
                     carrierServiceEligiblePackages.add(e.getKey());
                     // fallthrough
                 case PACKAGE_PRIVILEGED_FROM_CARRIER_CONFIG:
@@ -809,7 +754,9 @@
                 }
                 for (UiccAccessRule rule : mCarrierConfigRules) {
                     if (rule.matches(cert, pkgName)) {
-                        return PACKAGE_PRIVILEGED_FROM_CARRIER_CONFIG;
+                        return pkgName.equals(mTestOverrideCarrierServicePackage)
+                                ? PACKAGE_PRIVILEGED_FROM_CARRIER_SERVICE_TEST_OVERRIDE
+                                : PACKAGE_PRIVILEGED_FROM_CARRIER_CONFIG;
                     }
                 }
             }
@@ -882,33 +829,6 @@
     }
 
     /**
-     * Registers the given Registrant with this tracker.
-     *
-     * <p>After being registered, the Registrant will be notified with the current Carrier
-     * Privileged UIDs for this Phone.
-     *
-     * @deprecated Use {@link TelephonyManager#addCarrierPrivilegesListener} instead, which also
-     *     provides package names
-     *     <p>TODO(b/211658797) migrate callers, then delete all Registrant logic from CPT
-     */
-    @Deprecated
-    public void registerCarrierPrivilegesListener(@NonNull Handler h, int what,
-            @Nullable Object obj) {
-        sendMessage(obtainMessage(ACTION_REGISTER_LISTENER, new Registrant(h, what, obj)));
-    }
-
-    /**
-     * Unregisters the given listener with this tracker.
-     *
-     * @deprecated Use {@link TelephonyManager#removeCarrierPrivilegesListener} instead
-     *     <p>TODO(b/211658797) migrate callers, then delete all Registrant logic from CPT
-     */
-    @Deprecated
-    public void unregisterCarrierPrivilegesListener(@NonNull Handler handler) {
-        sendMessage(obtainMessage(ACTION_UNREGISTER_LISTENER, handler));
-    }
-
-    /**
      * Set test carrier privilege rules which will override the actual rules on both Carrier Config
      * and SIM.
      *
@@ -921,6 +841,30 @@
         sendMessage(obtainMessage(ACTION_SET_TEST_OVERRIDE_RULE, carrierPrivilegeRules));
     }
 
+    /**
+     * Override the carrier provisioning package, if it exists.
+     *
+     * <p>This API is to be used ONLY for testing, and requires the provided package to be carrier
+     * privileged. While this override is set, ONLY the specified package will be considered
+     * eligible to be bound as the carrier provisioning package, and any existing bindings will be
+     * terminated.
+     *
+     * @param carrierServicePackage the package to be used as the overridden carrier service
+     *     package, or {@code null} to reset override
+     * @see TelephonyManager#setCarrierServicePackageOverride
+     */
+    public void setTestOverrideCarrierServicePackage(@Nullable String carrierServicePackage) {
+        sendMessage(obtainMessage(
+                ACTION_SET_TEST_OVERRIDE_CARRIER_SERVICE_PACKAGE, carrierServicePackage));
+    }
+
+    private void handleSetTestOverrideCarrierServicePackage(
+            @Nullable String carrierServicePackage) {
+        mTestOverrideCarrierServicePackage = carrierServicePackage;
+        refreshInstalledPackageCache();
+        maybeUpdatePrivilegedPackagesAndNotifyRegistrants();
+    }
+
     private void handleSetTestOverrideRules(@Nullable String carrierPrivilegeRules) {
         if (carrierPrivilegeRules == null) {
             mTestOverrideRules = null;
@@ -1089,6 +1033,11 @@
         String carrierServicePackageName = null;
         for (ResolveInfo resolveInfo : carrierServiceResolveInfos) {
             String packageName = getPackageName(resolveInfo);
+            if (mTestOverrideCarrierServicePackage != null
+                    && !mTestOverrideCarrierServicePackage.equals(packageName)) {
+                continue;
+            }
+
             if (simPrivilegedPackages.contains(packageName)) {
                 carrierServicePackageName = packageName;
                 break;
@@ -1098,11 +1047,4 @@
                 ? new Pair<>(null, Process.INVALID_UID)
                 : new Pair<>(carrierServicePackageName, getPackageUid(carrierServicePackageName));
     }
-
-    @NonNull
-    private static int[] intSetToArray(@NonNull Set<Integer> intSet) {
-        IntArray converter = new IntArray(intSet.size());
-        intSet.forEach(converter::add);
-        return converter.toArray();
-    }
 }
diff --git a/src/java/com/android/internal/telephony/CarrierResolver.java b/src/java/com/android/internal/telephony/CarrierResolver.java
index ec6a5a0..8a9b3e3 100644
--- a/src/java/com/android/internal/telephony/CarrierResolver.java
+++ b/src/java/com/android/internal/telephony/CarrierResolver.java
@@ -43,6 +43,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.metrics.CarrierIdMatchStats;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.uicc.IccRecords;
 import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.telephony.util.TelephonyUtils;
@@ -54,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
@@ -191,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. ACTION_SUBINFO_CONTENT_CHANGE
+     *  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
@@ -547,7 +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.
-            SubscriptionController.getInstance().setCarrierId(mCarrierId, mPhone.getSubId());
+            SubscriptionManagerService.getInstance().setCarrierId(mPhone.getSubId(), mCarrierId);
         }
     }
 
@@ -751,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/CarrierServiceBindHelper.java b/src/java/com/android/internal/telephony/CarrierServiceBindHelper.java
index dfa53b3..960d794 100644
--- a/src/java/com/android/internal/telephony/CarrierServiceBindHelper.java
+++ b/src/java/com/android/internal/telephony/CarrierServiceBindHelper.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony;
 
+import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -200,7 +201,13 @@
         }
     }
 
-    void updateForPhoneId(int phoneId, String simState) {
+    /**
+     * Update SIM state.
+     *
+     * @param phoneId The phone id.
+     * @param simState The legacy SIM state.
+     */
+    public void updateForPhoneId(int phoneId, @NonNull String simState) {
         logdWithLocalLog("update binding for phoneId: " + phoneId + " simState: " + simState);
         if (!SubscriptionManager.isValidPhoneId(phoneId)) {
             return;
diff --git a/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java b/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
index 64dc7ec..6b44998 100644
--- a/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
@@ -19,10 +19,8 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.res.Resources;
 import android.os.Handler;
 import android.os.HandlerExecutor;
@@ -102,8 +100,34 @@
         this.mSST = sst;
         mTelephonyManager = mPhone.getContext().getSystemService(
                 TelephonyManager.class).createForSubscriptionId(mPhone.getSubId());
-        phone.getContext().registerReceiver(mBroadcastReceiver, new IntentFilter(
-                CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+        CarrierConfigManager ccm = mPhone.getContext().getSystemService(CarrierConfigManager.class);
+        ccm.registerCarrierConfigChangeListener(
+                mPhone.getContext().getMainExecutor(),
+                (slotIndex, subId, carrierId, specificCarrierId) -> {
+                    if (slotIndex != mPhone.getPhoneId()) return;
+
+                    Rlog.d(LOG_TAG, "onCarrierConfigChanged: slotIndex=" + slotIndex
+                            + ", subId=" + subId + ", carrierId=" + carrierId);
+
+                    // Only get carrier configs used for EmergencyNetworkNotification
+                    // and PrefNetworkNotification
+                    PersistableBundle b =
+                            CarrierConfigManager.getCarrierConfigSubset(
+                                    mPhone.getContext(),
+                                    mPhone.getSubId(),
+                                    CarrierConfigManager.KEY_EMERGENCY_NOTIFICATION_DELAY_INT,
+                                    CarrierConfigManager
+                                            .KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT);
+                    if (b.isEmpty()) return;
+
+                    for (Map.Entry<Integer, NotificationType> entry :
+                            mNotificationTypeMap.entrySet()) {
+                        NotificationType notificationType = entry.getValue();
+                        notificationType.setDelay(b);
+                    }
+                    handleConfigChanges();
+                });
+
         // Listen for subscriber changes
         SubscriptionManager.from(mPhone.getContext()).addOnSubscriptionsChangedListener(
                 new OnSubscriptionsChangedListener(this.getLooper()) {
@@ -246,7 +270,7 @@
         TelephonyManager tm = ((TelephonyManager) context.getSystemService(
                 Context.TELEPHONY_SERVICE)).createForSubscriptionId(mPhone.getSubId());
 
-        boolean isCarrierConfigEnabled = isCarrierConfigEnableNr(context);
+        boolean isCarrierConfigEnabled = isCarrierConfigEnableNr();
         boolean isRadioAccessFamilySupported = checkSupportedBitmask(
                 tm.getSupportedRadioAccessFamily(), TelephonyManager.NETWORK_TYPE_BITMASK_NR);
         boolean isNrNetworkTypeAllowed = checkSupportedBitmask(
@@ -261,15 +285,13 @@
         return (isCarrierConfigEnabled && isRadioAccessFamilySupported && isNrNetworkTypeAllowed);
     }
 
-    private boolean isCarrierConfigEnableNr(Context context) {
-        CarrierConfigManager carrierConfigManager = (CarrierConfigManager)
-                context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        if (carrierConfigManager == null) {
-            Rlog.e(LOG_TAG, "isCarrierConfigEnableNr: CarrierConfigManager is null");
-            return false;
-        }
-        PersistableBundle config = carrierConfigManager.getConfigForSubId(mPhone.getSubId());
-        if (config == null) {
+    private boolean isCarrierConfigEnableNr() {
+        PersistableBundle config =
+                CarrierConfigManager.getCarrierConfigSubset(
+                        mPhone.getContext(),
+                        mPhone.getSubId(),
+                        CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY);
+        if (config.isEmpty()) {
             Rlog.e(LOG_TAG, "isCarrierConfigEnableNr: Cannot get config " + mPhone.getSubId());
             return false;
         }
@@ -348,21 +370,6 @@
         return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
     }
 
-    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            CarrierConfigManager carrierConfigManager = (CarrierConfigManager)
-                    context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-            PersistableBundle b = carrierConfigManager.getConfigForSubId(mPhone.getSubId());
-
-            for (Map.Entry<Integer, NotificationType> entry : mNotificationTypeMap.entrySet()) {
-                NotificationType notificationType = entry.getValue();
-                notificationType.setDelay(b);
-            }
-            handleConfigChanges();
-        }
-    };
-
     /**
      * Post a notification to the NotificationManager for changing network type.
      */
@@ -376,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(
@@ -590,7 +598,7 @@
                     .setContentTitle(title)
                     .setStyle(new Notification.BigTextStyle().bigText(details))
                     .setContentText(details)
-                    .setFlag(Notification.FLAG_NO_CLEAR, true)
+                    .setOngoing(true)
                     .setChannelId(NotificationChannelController.CHANNEL_ID_WFC);
         }
     }
diff --git a/src/java/com/android/internal/telephony/CarrierSignalAgent.java b/src/java/com/android/internal/telephony/CarrierSignalAgent.java
index 2914ff9..3250cef 100644
--- a/src/java/com/android/internal/telephony/CarrierSignalAgent.java
+++ b/src/java/com/android/internal/telephony/CarrierSignalAgent.java
@@ -20,11 +20,8 @@
 
 import android.annotation.Nullable;
 import android.content.ActivityNotFoundException;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
-import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.net.ConnectivityManager;
 import android.net.Network;
@@ -48,7 +45,6 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -102,51 +98,45 @@
     /**
      * This is a list of supported signals from CarrierSignalAgent
      */
-    private static final Set<String> VALID_CARRIER_SIGNAL_ACTIONS = new HashSet<>(Arrays.asList(
+    private static final Set<String> VALID_CARRIER_SIGNAL_ACTIONS = Set.of(
             TelephonyManager.ACTION_CARRIER_SIGNAL_PCO_VALUE,
             TelephonyManager.ACTION_CARRIER_SIGNAL_REDIRECTED,
             TelephonyManager.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED,
             TelephonyManager.ACTION_CARRIER_SIGNAL_RESET,
-            TelephonyManager.ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE));
+            TelephonyManager.ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE);
 
-    private static final Map<String, String> NEW_ACTION_TO_COMPAT_MAP =
-            new HashMap<String, String>() {{
-                put(TelephonyManager.ACTION_CARRIER_SIGNAL_PCO_VALUE,
-                        TelephonyIntents.ACTION_CARRIER_SIGNAL_PCO_VALUE);
-                put(TelephonyManager.ACTION_CARRIER_SIGNAL_REDIRECTED,
-                        TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED);
-                put(TelephonyManager.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED,
-                        TelephonyIntents.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED);
-                put(TelephonyManager.ACTION_CARRIER_SIGNAL_RESET,
-                        TelephonyIntents.ACTION_CARRIER_SIGNAL_RESET);
-                put(TelephonyManager.ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE,
-                        TelephonyIntents.ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE);
-            }};
+    private static final Map<String, String> NEW_ACTION_TO_COMPAT_MAP = Map.of(
+            TelephonyManager.ACTION_CARRIER_SIGNAL_PCO_VALUE,
+                    TelephonyIntents.ACTION_CARRIER_SIGNAL_PCO_VALUE,
+            TelephonyManager.ACTION_CARRIER_SIGNAL_REDIRECTED,
+                    TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED,
+            TelephonyManager.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED,
+                    TelephonyIntents.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED,
+            TelephonyManager.ACTION_CARRIER_SIGNAL_RESET,
+                    TelephonyIntents.ACTION_CARRIER_SIGNAL_RESET,
+            TelephonyManager.ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE,
+                    TelephonyIntents.ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE);
 
     private static final Map<String, String> COMPAT_ACTION_TO_NEW_MAP = NEW_ACTION_TO_COMPAT_MAP
             .entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
 
     private final LocalLog mErrorLocalLog = new LocalLog(16);
 
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (DBG) log("CarrierSignalAgent receiver action: " + action);
-            if (action.equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
-                loadCarrierConfig();
-            }
-        }
-    };
-
     private ConnectivityManager.NetworkCallback mNetworkCallback;
 
     /** Constructor */
     public CarrierSignalAgent(Phone phone) {
         mPhone = phone;
+        CarrierConfigManager carrierConfigManager = mPhone.getContext().getSystemService(
+                CarrierConfigManager.class);
         loadCarrierConfig();
-        // reload configurations on CARRIER_CONFIG_CHANGED
-        mPhone.getContext().registerReceiver(mReceiver,
-                new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+        carrierConfigManager.registerCarrierConfigChangeListener(
+                mPhone.getContext().getMainExecutor(),
+                (slotIndex, subId, carrierId, specificCarrierId) -> {
+                    if (slotIndex == mPhone.getPhoneId()) {
+                        loadCarrierConfig();
+                    }
+                });
         mPhone.getCarrierActionAgent().registerForCarrierAction(
                 CarrierActionAgent.CARRIER_ACTION_REPORT_DEFAULT_NETWORK_STATUS, this,
                 EVENT_REGISTER_DEFAULT_NETWORK_AVAIL, null, false);
@@ -208,45 +198,47 @@
      * load carrier config and cached the results into a hashMap action -> array list of components.
      */
     private void loadCarrierConfig() {
-        CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
-                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        PersistableBundle b = null;
-        if (configManager != null) {
-            b = configManager.getConfigForSubId(mPhone.getSubId());
+        PersistableBundle b =
+                CarrierConfigManager.getCarrierConfigSubset(
+                        mPhone.getContext(),
+                        mPhone.getSubId(),
+                        KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY,
+                        KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY);
+        if (b.isEmpty()) {
+            return;
         }
-        if (b != null) {
-            synchronized (mCachedWakeSignalConfigs) {
-                log("Loading carrier config: " + KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY);
-                Map<String, Set<ComponentName>> config = parseAndCache(
-                        b.getStringArray(KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY));
-                // In some rare cases, up-to-date config could be fetched with delay and all signals
-                // have already been delivered the receivers from the default carrier config.
-                // To handle this raciness, we should notify those receivers (from old configs)
-                // and reset carrier actions. This should be done before cached Config got purged
-                // and written with the up-to-date value, Otherwise those receivers from the
-                // old config might lingers without properly clean-up.
-                if (!mCachedWakeSignalConfigs.isEmpty()
-                        && !config.equals(mCachedWakeSignalConfigs)) {
-                    if (VDBG) log("carrier config changed, reset receivers from old config");
-                    mPhone.getCarrierActionAgent().sendEmptyMessage(
-                            CarrierActionAgent.CARRIER_ACTION_RESET);
-                }
-                mCachedWakeSignalConfigs = config;
-            }
 
-            synchronized (mCachedNoWakeSignalConfigs) {
-                log("Loading carrier config: "
-                        + KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY);
-                Map<String, Set<ComponentName>> config = parseAndCache(
-                        b.getStringArray(KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY));
-                if (!mCachedNoWakeSignalConfigs.isEmpty()
-                        && !config.equals(mCachedNoWakeSignalConfigs)) {
-                    if (VDBG) log("carrier config changed, reset receivers from old config");
-                    mPhone.getCarrierActionAgent().sendEmptyMessage(
-                            CarrierActionAgent.CARRIER_ACTION_RESET);
-                }
-                mCachedNoWakeSignalConfigs = config;
+        synchronized (mCachedWakeSignalConfigs) {
+            log("Loading carrier config: " + KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY);
+            Map<String, Set<ComponentName>> config = parseAndCache(
+                    b.getStringArray(KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY));
+            // In some rare cases, up-to-date config could be fetched with delay and all signals
+            // have already been delivered the receivers from the default carrier config.
+            // To handle this raciness, we should notify those receivers (from old configs)
+            // and reset carrier actions. This should be done before cached Config got purged
+            // and written with the up-to-date value, Otherwise those receivers from the
+            // old config might lingers without properly clean-up.
+            if (!mCachedWakeSignalConfigs.isEmpty()
+                    && !config.equals(mCachedWakeSignalConfigs)) {
+                if (VDBG) log("carrier config changed, reset receivers from old config");
+                mPhone.getCarrierActionAgent().sendEmptyMessage(
+                        CarrierActionAgent.CARRIER_ACTION_RESET);
             }
+            mCachedWakeSignalConfigs = config;
+        }
+
+        synchronized (mCachedNoWakeSignalConfigs) {
+            log("Loading carrier config: "
+                    + KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY);
+            Map<String, Set<ComponentName>> config = parseAndCache(
+                    b.getStringArray(KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY));
+            if (!mCachedNoWakeSignalConfigs.isEmpty()
+                    && !config.equals(mCachedNoWakeSignalConfigs)) {
+                if (VDBG) log("carrier config changed, reset receivers from old config");
+                mPhone.getCarrierActionAgent().sendEmptyMessage(
+                        CarrierActionAgent.CARRIER_ACTION_RESET);
+            }
+            mCachedNoWakeSignalConfigs = config;
         }
     }
 
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 19a585d..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:
@@ -500,6 +504,14 @@
             final String rplmn = regResult.registeredPlmn;
             final int reasonForDenial = regResult.reasonForDenial;
 
+            if (regState == NetworkRegistrationInfo.REGISTRATION_STATE_DENIED
+                    && reasonForDenial
+                    == android.hardware.radio.network.RegistrationFailCause.NONE) {
+                AnomalyReporter.reportAnomaly(
+                        UUID.fromString("62ed270f-e139-418a-a427-8bcc1bca8f21"),
+                            "RIL Missing Reg Fail Reason", mPhone.getCarrierId());
+            }
+
             int networkType = ServiceState.rilRadioTechnologyToNetworkType(regResult.rat);
             if (networkType == TelephonyManager.NETWORK_TYPE_LTE_CA) {
                 networkType = TelephonyManager.NETWORK_TYPE_LTE;
@@ -514,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;
@@ -532,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,
@@ -557,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();
             }
         }
 
@@ -582,6 +614,14 @@
             networkType =
                     getNetworkTypeForCellIdentity(networkType, cellIdentity, mPhone.getCarrierId());
 
+            if (regState == NetworkRegistrationInfo.REGISTRATION_STATE_DENIED
+                    && reasonForDenial
+                    == android.hardware.radio.network.RegistrationFailCause.NONE) {
+                AnomalyReporter.reportAnomaly(
+                        UUID.fromString("62ed270f-e139-418a-a427-8bcc1bca8f21"),
+                            "RIL Missing Reg Fail Reason", mPhone.getCarrierId());
+            }
+
             // Conditional parameters for specific RANs
             boolean cssSupported = false;
             int roamingIndicator = 0;
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 c16cbaa..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,9 +55,9 @@
      */
     public void dataCallListChanged(int indicationType,
             android.hardware.radio.data.SetupDataCallResult[] dcList) {
-        mRil.processIndication(RIL.DATA_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_DATA, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_DATA_CALL_LIST_CHANGED, dcList);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_DATA_CALL_LIST_CHANGED, dcList);
         ArrayList<DataCallResponse> response = RILUtils.convertHalDataCallResultList(dcList);
         mRil.mDataCallListChangedRegistrants.notifyRegistrants(
                 new AsyncResult(null, response, null));
@@ -68,11 +70,12 @@
      */
     public void keepaliveStatus(int indicationType,
             android.hardware.radio.data.KeepaliveStatus halStatus) {
-        mRil.processIndication(RIL.DATA_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_DATA, indicationType);
 
-        if (RIL.RILJ_LOGD) {
-            mRil.unsljLogRet(RIL_UNSOL_KEEPALIVE_STATUS, "handle=" + halStatus.sessionHandle
-                    + " code=" +  halStatus.code);
+        if (mRil.isLogOrTrace()) {
+            mRil.unsljLogRet(
+                    RIL_UNSOL_KEEPALIVE_STATUS,
+                    "handle=" + halStatus.sessionHandle + " code=" + halStatus.code);
         }
 
         KeepaliveStatus ks = new KeepaliveStatus(
@@ -86,11 +89,11 @@
      * @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);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_PCO_DATA, response);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_PCO_DATA, response);
 
         mRil.mPcoDataRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
     }
@@ -103,10 +106,10 @@
      */
     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 (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_UNTHROTTLE_APN, response);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_UNTHROTTLE_APN, response);
 
         mRil.mApnUnthrottledRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
     }
@@ -119,8 +122,10 @@
      */
     public void slicingConfigChanged(int indicationType,
             android.hardware.radio.data.SlicingConfig slicingConfig) throws RemoteException {
-        mRil.processIndication(RIL.DATA_SERVICE, indicationType);
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_SLICING_CONFIG_CHANGED, slicingConfig);
+        mRil.processIndication(HAL_SERVICE_DATA, indicationType);
+        if (mRil.isLogOrTrace()) {
+            mRil.unsljLogRet(RIL_UNSOL_SLICING_CONFIG_CHANGED, slicingConfig);
+        }
         NetworkSlicingConfig ret = RILUtils.convertHalSlicingConfig(slicingConfig);
         mRil.mSlicingConfigChangedRegistrants.notifyRegistrants(
                 new AsyncResult(null, ret, null));
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 0b759b5..3341577 100644
--- a/src/java/com/android/internal/telephony/DebugService.java
+++ b/src/java/com/android/internal/telephony/DebugService.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony;
 
+import android.os.Build;
+
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.telephony.Rlog;
 
@@ -50,8 +52,16 @@
                     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().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 886a899..567331d 100644
--- a/src/java/com/android/internal/telephony/DisplayInfoController.java
+++ b/src/java/com/android/internal/telephony/DisplayInfoController.java
@@ -18,11 +18,11 @@
 
 import android.annotation.NonNull;
 import android.os.Handler;
+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;
 import android.util.IndentingPrintWriter;
@@ -45,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);
 
@@ -66,22 +64,36 @@
                     TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED)
             );
 
+    /** Event for service state changed (roaming). */
+    private static final int EVENT_SERVICE_STATE_CHANGED = 1;
+
     private final Phone mPhone;
     private final NetworkTypeController mNetworkTypeController;
     private final RegistrantList mTelephonyDisplayInfoChangedRegistrants = new RegistrantList();
-    private TelephonyDisplayInfo mTelephonyDisplayInfo;
+    private @NonNull TelephonyDisplayInfo mTelephonyDisplayInfo;
+    private @NonNull ServiceState mServiceState;
 
     public DisplayInfoController(Phone phone) {
         mPhone = phone;
         mLogTag = "DIC-" + mPhone.getPhoneId();
+        mTelephonyDisplayInfo = new TelephonyDisplayInfo(
+                TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE);
         mNetworkTypeController = new NetworkTypeController(phone, this);
         mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+
+        mServiceState = mPhone.getServiceStateTracker().getServiceState();
+        post(() -> {
+            mPhone.getServiceStateTracker()
+                    .registerForServiceStateChanged(this, EVENT_SERVICE_STATE_CHANGED, null);
+            updateTelephonyDisplayInfo();
+        });
     }
 
     /**
      * @return the current TelephonyDisplayInfo
      */
-    public TelephonyDisplayInfo getTelephonyDisplayInfo() {
+    public @NonNull TelephonyDisplayInfo getTelephonyDisplayInfo() {
         return mTelephonyDisplayInfo;
     }
 
@@ -90,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());
+        TelephonyDisplayInfo newDisplayInfo = new TelephonyDisplayInfo(
+                mNetworkTypeController.getDataNetworkType(),
+                mNetworkTypeController.getOverrideNetworkType(),
+                mServiceState.getRoaming());
         if (!newDisplayInfo.equals(mTelephonyDisplayInfo)) {
             logl("TelephonyDisplayInfo changed from " + mTelephonyDisplayInfo + " to "
                     + newDisplayInfo);
@@ -133,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());
         }
     }
@@ -157,6 +167,17 @@
         mTelephonyDisplayInfoChangedRegistrants.remove(h);
     }
 
+    @Override
+    public void handleMessage(@NonNull Message msg) {
+        switch (msg.what) {
+            case EVENT_SERVICE_STATE_CHANGED:
+                mServiceState = mPhone.getServiceStateTracker().getServiceState();
+                log("ServiceState updated, isRoaming=" + mServiceState.getRoaming());
+                updateTelephonyDisplayInfo();
+                break;
+        }
+    }
+
     /**
      * Log debug messages.
      * @param s debug messages
diff --git a/src/java/com/android/internal/telephony/FdnUtils.java b/src/java/com/android/internal/telephony/FdnUtils.java
new file mode 100644
index 0000000..aa2bcfd
--- /dev/null
+++ b/src/java/com/android/internal/telephony/FdnUtils.java
@@ -0,0 +1,190 @@
+/*
+ * 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.text.TextUtils;
+
+import com.android.i18n.phonenumbers.NumberParseException;
+import com.android.i18n.phonenumbers.PhoneNumberUtil;
+import com.android.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
+import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.uicc.AdnRecord;
+import com.android.internal.telephony.uicc.AdnRecordCache;
+import com.android.internal.telephony.uicc.IccConstants;
+import com.android.internal.telephony.uicc.IccRecords;
+import com.android.internal.telephony.uicc.UiccCardApplication;
+import com.android.internal.telephony.uicc.UiccController;
+import com.android.internal.telephony.uicc.UiccProfile;
+import com.android.telephony.Rlog;
+
+import java.util.ArrayList;
+
+/**
+ * This is a basic utility class for common functions related to Fixed Dialing Numbers
+ * designed as per 3GPP 22.101.
+ */
+public class FdnUtils {
+    private static final boolean VDBG = false;
+    private static final String LOG_TAG = FdnUtils.class.getSimpleName();
+
+    /**
+     * The following function checks if dialed number is blocked due to FDN.
+     *
+     * @param phoneId The phone object id for which the FDN check is performed
+     * @param dialStr dialed phone number
+     * @param defaultCountryIso country ISO for the subscription associated with this phone
+     * @return {@code true} if dialStr is blocked due to FDN check.
+     */
+    public static boolean isNumberBlockedByFDN(int phoneId, String dialStr,
+            String defaultCountryIso) {
+        if (!isFdnEnabled(phoneId)) {
+            return false;
+        }
+
+        ArrayList<AdnRecord> fdnList = getFdnList(phoneId);
+        return !isFDN(dialStr, defaultCountryIso, fdnList);
+    }
+
+    /**
+     * Checks if FDN is enabled
+     * @param phoneId The phone object id for which the FDN check is performed
+     * @return {@code true} if FDN is enabled
+     */
+    public static boolean isFdnEnabled(int phoneId) {
+        UiccCardApplication app = getUiccCardApplication(phoneId);
+        if (app == null || (!app.getIccFdnAvailable())) {
+            return false;
+        }
+
+        return app.getIccFdnEnabled();
+    }
+
+    /**
+     * If FDN is enabled, check to see if the given supplementary service control strings are
+     * blocked due to FDN.
+     * @param phoneId The phone object id for which the FDN check is performed
+     * @param controlStrings control strings associated with the supplementary service request
+     * @param defaultCountryIso country ISO for the subscription associated with this phone
+     * @return {@code true} if the FDN list does not contain any of the control strings.
+     */
+    public static boolean isSuppServiceRequestBlockedByFdn(int phoneId,
+            ArrayList<String> controlStrings, String defaultCountryIso) {
+        if (!isFdnEnabled(phoneId)) {
+            return false;
+        }
+
+        ArrayList<AdnRecord> fdnList = getFdnList(phoneId);
+        for(String controlString : controlStrings) {
+            if(isFDN(controlString, defaultCountryIso, fdnList)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Checks if dialStr is part of FDN list.
+     *
+     * @param fdnList List of all FDN records associated with a sim card
+     * @param dialStr dialed phone number
+     * @param defaultCountryIso country ISO for the subscription associated with this phone
+     * @return {@code true} if dialStr is present in the fdnList.
+     */
+    @VisibleForTesting
+    public static boolean isFDN(String dialStr, String defaultCountryIso,
+            ArrayList<AdnRecord> fdnList) {
+        if (fdnList == null || fdnList.isEmpty() || TextUtils.isEmpty(dialStr)) {
+            Rlog.w(LOG_TAG, "isFDN: unexpected null value");
+            return false;
+        }
+
+        // Parse the dialStr and convert it to E164 format
+        String dialStrE164 = null;
+        String dialStrNational = null;
+        final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
+        try {
+            PhoneNumber phoneNumber = phoneNumberUtil.parse(dialStr, defaultCountryIso);
+            dialStrE164 = phoneNumberUtil.format(phoneNumber, PhoneNumberFormat.E164);
+            dialStrNational = String.valueOf(phoneNumber.getNationalNumber());
+        } catch (NumberParseException ignored) {
+            Rlog.w(LOG_TAG, "isFDN: could not parse dialStr");
+        }
+
+        /**
+         * Returns true if dialStrE164 or dialStrNational or dialStr starts with fdnNumber
+         * E.g.1: returns true if fdnNumber="123" and dialStr="12345"
+         * E.g.2: does not return true if fdnNumber="1123" and dialStr="12345"
+         */
+        for (AdnRecord fdn: fdnList) {
+            String fdnNumber = fdn.getNumber();
+            if (TextUtils.isEmpty(fdnNumber)) {
+                continue;
+            }
+
+            if(!TextUtils.isEmpty(dialStrE164)) {
+                if(dialStrE164.startsWith(fdnNumber)) {
+                    return true;
+                }
+            }
+
+            if(!TextUtils.isEmpty(dialStrNational)) {
+                if (dialStrNational.startsWith(fdnNumber)) {
+                    return true;
+                }
+            }
+
+            if (dialStr.startsWith(fdnNumber)) {
+                return true;
+            }
+        }
+
+        if (VDBG) {
+            Rlog.i(LOG_TAG, "isFDN: dialed number not present in FDN list");
+        }
+        return false;
+    }
+
+    private static ArrayList<AdnRecord> getFdnList(int phoneId) {
+        UiccCardApplication app = getUiccCardApplication(phoneId);
+        if (app == null) {
+            return null;
+        }
+
+        IccRecords iccRecords = app.getIccRecords();
+        if (iccRecords == null) {
+            return null;
+        }
+
+        AdnRecordCache adnRecordCache = iccRecords.getAdnCache();
+        if(adnRecordCache == null) {
+            return null;
+        }
+
+        return adnRecordCache.getRecordsIfLoaded(IccConstants.EF_FDN);
+    }
+
+    private static UiccCardApplication getUiccCardApplication(int phoneId) {
+        UiccProfile uiccProfile = UiccController.getInstance()
+                .getUiccProfileForPhone(phoneId);
+        if (uiccProfile == null) {
+            return null;
+        }
+
+        return uiccProfile.getApplication(UiccController.APP_FAM_3GPP);
+    }
+}
\ No newline at end of file
diff --git a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
old mode 100755
new mode 100644
index 3293558..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;
 
@@ -182,11 +184,6 @@
             mCi.unregisterForCallWaitingInfo(this);
             // Prior to phone switch to GSM, if CDMA has any emergency call
             // data will be in disabled state, after switching to GSM enable data.
-            if (mIsInEmergencyCall) {
-                if (!mPhone.isUsingNewDataStack()) {
-                    mPhone.getDataEnabledSettings().setInternalDataEnabled(true);
-                }
-            }
         } else {
             mConnections = new GsmCdmaConnection[MAX_CONNECTIONS_CDMA];
             mPendingCallInEcm = false;
@@ -400,9 +397,6 @@
     //CDMA
     public void setIsInEmergencyCall() {
         mIsInEmergencyCall = true;
-        if (!mPhone.isUsingNewDataStack()) {
-            mPhone.getDataEnabledSettings().setInternalDataEnabled(false);
-        }
         mPhone.notifyEmergencyCallRegistrants(true);
         mPhone.sendEmergencyCallStateChange(true);
     }
@@ -488,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;
             }
         }
 
@@ -967,6 +974,8 @@
                             } else {
                                 newUnknownConnectionCdma = mConnections[i];
                             }
+                        } else if (hangupWaitingCallSilently(i)) {
+                            return;
                         }
                     }
                 }
@@ -1018,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) */
@@ -1754,9 +1766,6 @@
             }
             if (!inEcm) {
                 // Re-initiate data connection
-                if (!mPhone.isUsingNewDataStack()) {
-                    mPhone.getDataEnabledSettings().setInternalDataEnabled(true);
-                }
                 mPhone.notifyEmergencyCallRegistrants(false);
             }
             mPhone.sendEmergencyCallStateChange(false);
@@ -1830,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;
@@ -1909,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/GsmCdmaConnection.java b/src/java/com/android/internal/telephony/GsmCdmaConnection.java
index b79bdef..e06520a 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaConnection.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaConnection.java
@@ -478,7 +478,7 @@
      * @return the corresponding value from {@link DisconnectCause}
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    int disconnectCauseFromCode(int causeCode) {
+    public int disconnectCauseFromCode(int causeCode) {
         /**
          * See 22.001 Annex F.4 for mapping of cause codes
          * to local tones
@@ -565,6 +565,14 @@
             case CallFailCause.RADIO_OFF:
                 return DisconnectCause.POWER_OFF;
 
+            case CallFailCause.NO_VALID_SIM:
+                return DisconnectCause.ICC_ERROR;
+
+            case CallFailCause.LOCAL_NETWORK_NO_SERVICE:
+                // fallthrough
+            case CallFailCause.LOCAL_SERVICE_UNAVAILABLE:
+                return DisconnectCause.OUT_OF_SERVICE;
+
             case CallFailCause.ACCESS_CLASS_BLOCKED:
             case CallFailCause.ERROR_UNSPECIFIED:
             case CallFailCause.NORMAL_CLEARING:
diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
index ff8412f..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,11 +72,13 @@
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
-import android.telephony.AccessNetworkConstants;
+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;
@@ -82,7 +92,7 @@
 import android.telephony.TelephonyManager;
 import android.telephony.UiccAccessRule;
 import android.telephony.UssdResponse;
-import android.telephony.data.ApnSetting;
+import android.telephony.ims.ImsCallProfile;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
@@ -94,17 +104,19 @@
 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.dataconnection.DataEnabledSettings;
-import com.android.internal.telephony.dataconnection.DcTracker;
-import com.android.internal.telephony.dataconnection.TransportManager;
+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;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
 import com.android.internal.telephony.imsphone.ImsPhoneMmiCode;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.metrics.VoiceCallSessionStats;
+import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
+import com.android.internal.telephony.subscription.SubscriptionManagerService.SubscriptionManagerServiceCallback;
 import com.android.internal.telephony.test.SimulatedRadioControl;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
 import com.android.internal.telephony.uicc.IccCardStatus;
@@ -131,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;
@@ -157,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
@@ -227,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 &
@@ -271,12 +295,11 @@
     private CarrierKeyDownloadManager mCDM;
     private CarrierInfoManager mCIM;
 
-    private final SettingsObserver mSettingsObserver;
-
     private final ImsManagerFactory mImsManagerFactory;
     private final CarrierPrivilegesTracker mCarrierPrivilegesTracker;
 
     private final SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionsChangedListener;
+    private final CallWaitingController mCallWaitingController;
 
     // Constructors
 
@@ -316,10 +339,6 @@
         mAccessNetworksManager = mTelephonyComponentFactory
                 .inject(AccessNetworksManager.class.getName())
                 .makeAccessNetworksManager(this, getLooper());
-        if (!isUsingNewDataStack()) {
-            mTransportManager = mTelephonyComponentFactory.inject(TransportManager.class.getName())
-                    .makeTransportManager(this);
-        }
         // SST/DSM depends on SSC, so SSC is instanced before SST/DSM
         mSignalStrengthController = mTelephonyComponentFactory.inject(
                 SignalStrengthController.class.getName()).makeSignalStrengthController(this);
@@ -328,10 +347,6 @@
         mEmergencyNumberTracker = mTelephonyComponentFactory
                 .inject(EmergencyNumberTracker.class.getName()).makeEmergencyNumberTracker(
                         this, this.mCi);
-        if (!isUsingNewDataStack()) {
-            mDataEnabledSettings = mTelephonyComponentFactory
-                    .inject(DataEnabledSettings.class.getName()).makeDataEnabledSettings(this);
-        }
         mDeviceStateMonitor = mTelephonyComponentFactory.inject(DeviceStateMonitor.class.getName())
                 .makeDeviceStateMonitor(this);
 
@@ -340,20 +355,9 @@
         mDisplayInfoController = mTelephonyComponentFactory.inject(
                 DisplayInfoController.class.getName()).makeDisplayInfoController(this);
 
-        if (isUsingNewDataStack()) {
-            mDataNetworkController = mTelephonyComponentFactory.inject(
-                    DataNetworkController.class.getName())
-                    .makeDataNetworkController(this, getLooper());
-        } else {
-            // DcTracker uses ServiceStateTracker and DisplayInfoController so needs to be created
-            // after they are instantiated
-            for (int transport : mAccessNetworksManager.getAvailableTransports()) {
-                DcTracker dcTracker = mTelephonyComponentFactory.inject(DcTracker.class.getName())
-                        .makeDcTracker(this, transport);
-                mDcTrackers.put(transport, dcTracker);
-                mAccessNetworksManager.registerDataThrottler(dcTracker.getDataThrottler());
-            }
-        }
+        mDataNetworkController = mTelephonyComponentFactory.inject(
+                DataNetworkController.class.getName())
+                .makeDataNetworkController(this, getLooper());
 
         mCarrierResolver = mTelephonyComponentFactory.inject(CarrierResolver.class.getName())
                 .makeCarrierResolver(this);
@@ -365,23 +369,22 @@
 
         mSST.registerForNetworkAttached(this, EVENT_REGISTERED_TO_NETWORK, null);
         mSST.registerForVoiceRegStateOrRatChanged(this, EVENT_VRS_OR_RAT_CHANGED, null);
+        mSST.getServiceStateStats().registerDataNetworkControllerCallback();
 
-        // TODO: Remove SettingsObserver and provisioning events when DataEnabledSettings is removed
-        mSettingsObserver = new SettingsObserver(context, this);
-        mSettingsObserver.observe(
-                Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
-                EVENT_DEVICE_PROVISIONED_CHANGE);
-        mSettingsObserver.observe(
-                Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED),
-                EVENT_DEVICE_PROVISIONING_DATA_SETTING_CHANGE);
-
-        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);
@@ -419,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);
+                    }
+                }
             }
         }
     };
@@ -475,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) {
@@ -506,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);
@@ -530,7 +553,7 @@
                 logd("update icc_operator_numeric=" + operatorNumeric);
                 tm.setSimOperatorNumericForPhone(mPhoneId, operatorNumeric);
 
-                SubscriptionController.getInstance().setMccMnc(operatorNumeric, getSubId());
+                mSubscriptionManagerService.setMccMnc(getSubId(), operatorNumeric);
 
                 // Sets iso country property by retrieving from build-time system property
                 String iso = "";
@@ -542,7 +565,7 @@
 
                 logd("init: set 'gsm.sim.operator.iso-country' to iso=" + iso);
                 tm.setSimCountryIsoForPhone(mPhoneId, iso);
-                SubscriptionController.getInstance().setCountryIso(iso, getSubId());
+                mSubscriptionManagerService.setCountryIso(getSubId(), iso);
 
                 // Updates MCC MNC device configuration information
                 logd("update mccmnc=" + operatorNumeric);
@@ -554,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;
@@ -680,11 +728,6 @@
     }
 
     @Override
-    public TransportManager getTransportManager() {
-        return mTransportManager;
-    }
-
-    @Override
     public AccessNetworksManager getAccessNetworksManager() {
         return mAccessNetworksManager;
     }
@@ -736,83 +779,8 @@
     }
 
     @Override
-    public PhoneConstants.DataState getDataConnectionState(String apnType) {
-        PhoneConstants.DataState ret = PhoneConstants.DataState.DISCONNECTED;
-
-        if (mSST == null) {
-            // Radio Technology Change is ongoing, dispose() and removeReferences() have
-            // already been called
-
-            ret = PhoneConstants.DataState.DISCONNECTED;
-        } else if (mSST.getCurrentDataConnectionState() != ServiceState.STATE_IN_SERVICE
-                && (isPhoneTypeCdma() || isPhoneTypeCdmaLte() ||
-                (isPhoneTypeGsm() && !apnType.equals(ApnSetting.TYPE_EMERGENCY_STRING)))) {
-            // If we're out of service, open TCP sockets may still work
-            // but no data will flow
-
-            // Emergency APN is available even in Out Of Service
-            // Pass the actual State of EPDN
-
-            ret = PhoneConstants.DataState.DISCONNECTED;
-        } else { /* mSST.gprsState == ServiceState.STATE_IN_SERVICE */
-            int currentTransport = mAccessNetworksManager.getCurrentTransport(
-                    ApnSetting.getApnTypesBitmaskFromString(apnType));
-            if (getDcTracker(currentTransport) != null) {
-                switch (getDcTracker(currentTransport).getState(apnType)) {
-                    case CONNECTED:
-                    case DISCONNECTING:
-                        if (isDataSuspended()) {
-                            ret = PhoneConstants.DataState.SUSPENDED;
-                        } else {
-                            ret = PhoneConstants.DataState.CONNECTED;
-                        }
-                        break;
-                    case CONNECTING:
-                        ret = PhoneConstants.DataState.CONNECTING;
-                        break;
-                    default:
-                        ret = PhoneConstants.DataState.DISCONNECTED;
-                }
-            }
-        }
-
-        logd("getDataConnectionState apnType=" + apnType + " ret=" + ret);
-        return ret;
-    }
-
-    @Override
     public @DataActivityType int getDataActivityState() {
-        if (isUsingNewDataStack()) {
-            return getDataNetworkController().getDataActivity();
-        }
-        int ret = TelephonyManager.DATA_ACTIVITY_NONE;
-
-        if (mSST.getCurrentDataConnectionState() == ServiceState.STATE_IN_SERVICE
-                && getDcTracker(AccessNetworkConstants.TRANSPORT_TYPE_WWAN) != null) {
-            switch (getDcTracker(AccessNetworkConstants.TRANSPORT_TYPE_WWAN).getActivity()) {
-                case DATAIN:
-                    ret = TelephonyManager.DATA_ACTIVITY_IN;
-                break;
-
-                case DATAOUT:
-                    ret = TelephonyManager.DATA_ACTIVITY_OUT;
-                break;
-
-                case DATAINANDOUT:
-                    ret = TelephonyManager.DATA_ACTIVITY_INOUT;
-                break;
-
-                case DORMANT:
-                    ret = TelephonyManager.DATA_ACTIVITY_DORMANT;
-                break;
-
-                default:
-                    ret = TelephonyManager.DATA_ACTIVITY_NONE;
-                break;
-            }
-        }
-
-        return ret;
+        return getDataNetworkController().getDataActivity();
     }
 
     /**
@@ -831,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) {
@@ -1023,14 +994,6 @@
     }
 
     @Override
-    public void dispose() {
-        // Note: this API is currently never called. We are defining actions here in case
-        // we need to dispose GsmCdmaPhone/Phone object.
-        super.dispose();
-        SubscriptionController.getInstance().unregisterForUiccAppsEnabled(this);
-    }
-
-    @Override
     public void enableEnhancedVoicePrivacy(boolean enable, Message onComplete) {
         if (isPhoneTypeGsm()) {
             loge("enableEnhancedVoicePrivacy: not expected on GSM");
@@ -1434,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));
@@ -1444,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)
@@ -1469,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
@@ -1491,6 +1508,12 @@
                     + ((imsPhone != null) ? imsPhone.getServiceState().getState() : "N/A"));
         }
 
+        // Perform FDN check for non-emergency calls - shouldn't dial if number is blocked by FDN
+        if(!isEmergency && FdnUtils.isNumberBlockedByFDN(mPhoneId, dialString, getCountryIso())) {
+            throw new CallStateException(CallStateException.ERROR_FDN_BLOCKED,
+                    "cannot dial number blocked by FDN");
+        }
+
         // Bypass WiFi Only WFC check if this is an emergency call - we should still try to
         // place over cellular if possible.
         if (!isEmergency) {
@@ -1559,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);
@@ -1688,6 +1714,13 @@
             return true;
         }
 
+        // Perform FDN check
+        if(FdnUtils.isNumberBlockedByFDN(mPhoneId, ussdRequest, getCountryIso())) {
+            sendUssdResponse(ussdRequest, null, TelephonyManager.USSD_RETURN_FAILURE,
+                    wrappedCallback );
+            return true;
+        }
+
         // Try over IMS if possible.
         Phone imsPhone = mImsPhone;
         if ((imsPhone != null)
@@ -1787,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
@@ -1797,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();
@@ -1946,6 +1984,11 @@
         return mImei;
     }
 
+    @Override
+    public int getImeiType() {
+        return mImeiType;
+    }
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @Override
     public String getEsn() {
@@ -2331,6 +2374,15 @@
     @Override
     public void getCallForwardingOption(int commandInterfaceCFReason, int serviceClass,
             Message onComplete) {
+        // Perform FDN check
+        SsData.ServiceType serviceType = GsmMmiCode.cfReasonToServiceType(commandInterfaceCFReason);
+        if(isRequestBlockedByFDN(SsData.RequestType.SS_INTERROGATION, serviceType)) {
+            AsyncResult.forMessage(onComplete, null,
+                    new CommandException(CommandException.Error.FDN_CHECK_FAILURE));
+            onComplete.sendToTarget();
+            return;
+        }
+
         Phone imsPhone = mImsPhone;
         if (useSsOverIms(onComplete)) {
             imsPhone.getCallForwardingOption(commandInterfaceCFReason, serviceClass, onComplete);
@@ -2380,6 +2432,16 @@
             int serviceClass,
             int timerSeconds,
             Message onComplete) {
+        // Perform FDN check
+        SsData.RequestType requestType = GsmMmiCode.cfActionToRequestType(commandInterfaceCFAction);
+        SsData.ServiceType serviceType = GsmMmiCode.cfReasonToServiceType(commandInterfaceCFReason);
+        if(isRequestBlockedByFDN(requestType, serviceType)) {
+            AsyncResult.forMessage(onComplete, null,
+                    new CommandException(CommandException.Error.FDN_CHECK_FAILURE));
+            onComplete.sendToTarget();
+            return;
+        }
+
         Phone imsPhone = mImsPhone;
         if (useSsOverIms(onComplete)) {
             imsPhone.setCallForwardingOption(commandInterfaceCFAction, commandInterfaceCFReason,
@@ -2433,6 +2495,15 @@
     @Override
     public void getCallBarring(String facility, String password, Message onComplete,
             int serviceClass) {
+        // Perform FDN check
+        SsData.ServiceType serviceType = GsmMmiCode.cbFacilityToServiceType(facility);
+        if (isRequestBlockedByFDN(SsData.RequestType.SS_INTERROGATION, serviceType)) {
+            AsyncResult.forMessage(onComplete, null,
+                    new CommandException(CommandException.Error.FDN_CHECK_FAILURE));
+            onComplete.sendToTarget();
+            return;
+        }
+
         Phone imsPhone = mImsPhone;
         if (useSsOverIms(onComplete)) {
             imsPhone.getCallBarring(facility, password, onComplete, serviceClass);
@@ -2449,6 +2520,17 @@
     @Override
     public void setCallBarring(String facility, boolean lockState, String password,
             Message onComplete, int serviceClass) {
+        // Perform FDN check
+        SsData.RequestType requestType = lockState ? SsData.RequestType.SS_ACTIVATION :
+                SsData.RequestType.SS_DEACTIVATION;
+        SsData.ServiceType serviceType = GsmMmiCode.cbFacilityToServiceType(facility);
+        if (isRequestBlockedByFDN(requestType, serviceType)) {
+            AsyncResult.forMessage(onComplete, null,
+                    new CommandException(CommandException.Error.FDN_CHECK_FAILURE));
+            onComplete.sendToTarget();
+            return;
+        }
+
         Phone imsPhone = mImsPhone;
         if (useSsOverIms(onComplete)) {
             imsPhone.setCallBarring(facility, lockState, password, onComplete, serviceClass);
@@ -2472,6 +2554,18 @@
      */
     public void changeCallBarringPassword(String facility, String oldPwd, String newPwd,
             Message onComplete) {
+        // Perform FDN check
+        SsData.ServiceType serviceType = GsmMmiCode.cbFacilityToServiceType(facility);
+        ArrayList<String> controlStrings = GsmMmiCode.getControlStringsForPwd(
+                SsData.RequestType.SS_REGISTRATION,
+                serviceType);
+        if(FdnUtils.isSuppServiceRequestBlockedByFdn(mPhoneId, controlStrings, getCountryIso())) {
+            AsyncResult.forMessage(onComplete, null,
+                    new CommandException(CommandException.Error.FDN_CHECK_FAILURE));
+            onComplete.sendToTarget();
+            return;
+        }
+
         if (isPhoneTypeGsm()) {
             mCi.changeBarringPassword(facility, oldPwd, newPwd, onComplete);
         } else {
@@ -2481,7 +2575,16 @@
 
     @Override
     public void getOutgoingCallerIdDisplay(Message onComplete) {
+        // Perform FDN check
+        if(isRequestBlockedByFDN(SsData.RequestType.SS_INTERROGATION, SsData.ServiceType.SS_CLIR)){
+            AsyncResult.forMessage(onComplete, null,
+                    new CommandException(CommandException.Error.FDN_CHECK_FAILURE));
+            onComplete.sendToTarget();
+            return;
+        }
+
         Phone imsPhone = mImsPhone;
+
         if (useSsOverIms(onComplete)) {
             imsPhone.getOutgoingCallerIdDisplay(onComplete);
             return;
@@ -2499,6 +2602,15 @@
 
     @Override
     public void setOutgoingCallerIdDisplay(int commandInterfaceCLIRMode, Message onComplete) {
+        // Perform FDN check
+        SsData.RequestType requestType = GsmMmiCode.clirModeToRequestType(commandInterfaceCLIRMode);
+        if (isRequestBlockedByFDN(requestType, SsData.ServiceType.SS_CLIR)) {
+            AsyncResult.forMessage(onComplete, null,
+                    new CommandException(CommandException.Error.FDN_CHECK_FAILURE));
+            onComplete.sendToTarget();
+            return;
+        }
+
         Phone imsPhone = mImsPhone;
         if (useSsOverIms(onComplete)) {
             imsPhone.setOutgoingCallerIdDisplay(commandInterfaceCLIRMode, onComplete);
@@ -2521,6 +2633,14 @@
 
     @Override
     public void queryCLIP(Message onComplete) {
+        // Perform FDN check
+        if(isRequestBlockedByFDN(SsData.RequestType.SS_INTERROGATION, SsData.ServiceType.SS_CLIP)){
+            AsyncResult.forMessage(onComplete, null,
+                    new CommandException(CommandException.Error.FDN_CHECK_FAILURE));
+            onComplete.sendToTarget();
+            return;
+        }
+
         Phone imsPhone = mImsPhone;
         if (useSsOverIms(onComplete)) {
             imsPhone.queryCLIP(onComplete);
@@ -2539,6 +2659,16 @@
 
     @Override
     public void getCallWaiting(Message onComplete) {
+        // Perform FDN check
+        if(isRequestBlockedByFDN(SsData.RequestType.SS_INTERROGATION, SsData.ServiceType.SS_WAIT)){
+            AsyncResult.forMessage(onComplete, null,
+                    new CommandException(CommandException.Error.FDN_CHECK_FAILURE));
+            onComplete.sendToTarget();
+            return;
+        }
+
+        if (mCallWaitingController.getCallWaiting(onComplete)) return;
+
         Phone imsPhone = mImsPhone;
         if (useSsOverIms(onComplete)) {
             imsPhone.getCallWaiting(onComplete);
@@ -2580,6 +2710,18 @@
 
     @Override
     public void setCallWaiting(boolean enable, int serviceClass, Message onComplete) {
+        // Perform FDN check
+        SsData.RequestType requestType = enable ? SsData.RequestType.SS_ACTIVATION :
+                SsData.RequestType.SS_DEACTIVATION;
+        if (isRequestBlockedByFDN(requestType, SsData.ServiceType.SS_WAIT)) {
+            AsyncResult.forMessage(onComplete, null,
+                    new CommandException(CommandException.Error.FDN_CHECK_FAILURE));
+            onComplete.sendToTarget();
+            return;
+        }
+
+        if (mCallWaitingController.setCallWaiting(enable, serviceClass, onComplete)) return;
+
         Phone imsPhone = mImsPhone;
         if (useSsOverIms(onComplete)) {
             imsPhone.setCallWaiting(enable, onComplete);
@@ -2610,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);
@@ -2672,25 +2831,12 @@
 
     @Override
     public boolean getDataRoamingEnabled() {
-        if (isUsingNewDataStack()) {
-            return getDataSettingsManager().isDataRoamingEnabled();
-        }
-        if (getDcTracker(AccessNetworkConstants.TRANSPORT_TYPE_WWAN) != null) {
-            return getDcTracker(AccessNetworkConstants.TRANSPORT_TYPE_WWAN).getDataRoamingEnabled();
-        }
-        return false;
+        return getDataSettingsManager().isDataRoamingEnabled();
     }
 
     @Override
     public void setDataRoamingEnabled(boolean enable) {
-        if (isUsingNewDataStack()) {
-            getDataSettingsManager().setDataRoamingEnabled(enable);
-            return;
-        }
-        if (getDcTracker(AccessNetworkConstants.TRANSPORT_TYPE_WWAN) != null) {
-            getDcTracker(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                    .setDataRoamingEnabledByUser(enable);
-        }
+        getDataSettingsManager().setDataRoamingEnabled(enable);
     }
 
     @Override
@@ -2735,21 +2881,12 @@
     }
 
     /**
-     * Whether data is enabled by user. Unlike isDataEnabled, this only
-     * checks user setting stored in {@link android.provider.Settings.Global#MOBILE_DATA}
-     * if not provisioning, or isProvisioningDataEnabled if provisioning.
+     * Whether data is enabled by user.
      */
     @Override
     public boolean isUserDataEnabled() {
-        if (isUsingNewDataStack()) {
-            return getDataSettingsManager().isDataEnabledForReason(
-                    TelephonyManager.DATA_ENABLED_REASON_USER);
-        }
-        if (mDataEnabledSettings.isProvisioning()) {
-            return mDataEnabledSettings.isProvisioningDataEnabled();
-        } else {
-            return mDataEnabledSettings.isUserDataEnabled();
-        }
+        return getDataSettingsManager().isDataEnabledForReason(
+                TelephonyManager.DATA_ENABLED_REASON_USER);
     }
 
     /**
@@ -2901,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();
     }
 
@@ -2951,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;
 
@@ -2959,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
@@ -2985,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);
+                        }
                     }
                 }
             }
@@ -3283,24 +3441,9 @@
             case EVENT_SET_CARRIER_DATA_ENABLED:
                 ar = (AsyncResult) msg.obj;
                 boolean enabled = (boolean) ar.result;
-                if (isUsingNewDataStack()) {
-                    getDataSettingsManager().setDataEnabled(
-                            TelephonyManager.DATA_ENABLED_REASON_CARRIER, enabled,
-                            mContext.getOpPackageName());
-                    return;
-                }
-                mDataEnabledSettings.setDataEnabled(TelephonyManager.DATA_ENABLED_REASON_CARRIER,
-                        enabled);
-                break;
-            case EVENT_DEVICE_PROVISIONED_CHANGE:
-                if (!isUsingNewDataStack()) {
-                    mDataEnabledSettings.updateProvisionedChanged();
-                }
-                break;
-            case EVENT_DEVICE_PROVISIONING_DATA_SETTING_CHANGE:
-                if (!isUsingNewDataStack()) {
-                    mDataEnabledSettings.updateProvisioningDataEnabled();
-                }
+                getDataSettingsManager().setDataEnabled(
+                        TelephonyManager.DATA_ENABLED_REASON_CARRIER, enabled,
+                        mContext.getOpPackageName());
                 break;
             case EVENT_GET_AVAILABLE_NETWORKS_DONE:
                 ar = (AsyncResult) msg.obj;
@@ -3375,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);
@@ -3747,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());
@@ -3770,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="
@@ -3795,10 +3998,6 @@
 
             // send an Intent
             sendEmergencyCallbackModeChange();
-            // Re-initiate data connection
-            if (!isUsingNewDataStack()) {
-                mDataEnabledSettings.setInternalDataEnabled(true);
-            }
             notifyEmergencyCallRegistrants(false);
         }
         mIsTestingEmergencyCallbackMode = false;
@@ -3816,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);
@@ -4229,6 +4429,7 @@
     @Override
     public void setImsRegistrationState(boolean registered) {
         mSST.setImsRegistrationState(registered);
+        mCallWaitingController.setImsRegistrationState(registered);
     }
 
     @Override
@@ -4290,6 +4491,13 @@
                 " mTelecomVoiceServiceStateOverride=" + mTelecomVoiceServiceStateOverride + "("
                         + ServiceState.rilServiceStateToString(mTelecomVoiceServiceStateOverride)
                         + ")");
+        pw.println(" mUiccApplicationsEnabled=" + mUiccApplicationsEnabled);
+        pw.flush();
+        try {
+            mCallWaitingController.dump(pw);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
         pw.flush();
     }
 
@@ -4409,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) {
@@ -4515,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
@@ -4675,6 +4904,8 @@
         // If no card is present or we don't have mUiccApplicationsEnabled yet, do nothing.
         if (slot == null || slot.getCardState() != IccCardStatus.CardState.CARDSTATE_PRESENT
                 || mUiccApplicationsEnabled == null) {
+            loge("reapplyUiccAppsEnablementIfNeeded: slot state="
+                    + (slot != null ? slot.getCardState() : null));
             return;
         }
 
@@ -4687,18 +4918,24 @@
             return;
         }
 
-        SubscriptionInfo 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);
 
         // If info is null, it could be a new subscription. By default we enable it.
-        boolean expectedValue = info == null ? true : info.areUiccApplicationsEnabled();
+        boolean expectedValue = info == null || info.areUiccApplicationsEnabled();
 
         // If for any reason current state is different from configured state, re-apply the
         // configured state.
         if (expectedValue != mUiccApplicationsEnabled) {
             mCi.enableUiccApplications(expectedValue, Message.obtain(
                     this, EVENT_REAPPLY_UICC_APPS_ENABLEMENT_DONE,
-                    new Pair<Boolean, Integer>(expectedValue, retries)));
+                    new Pair<>(expectedValue, retries)));
         }
     }
 
@@ -4745,24 +4982,7 @@
      * @return Currently bound data service package names.
      */
     public @NonNull List<String> getDataServicePackages() {
-        if (isUsingNewDataStack()) {
-            return getDataNetworkController().getDataServicePackages();
-        }
-        List<String> packages = new ArrayList<>();
-        int[] transports = new int[]{AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
-                AccessNetworkConstants.TRANSPORT_TYPE_WLAN};
-
-        for (int transport : transports) {
-            DcTracker dct = getDcTracker(transport);
-            if (dct != null) {
-                String pkg = dct.getDataServiceManager().getDataServicePackageName();
-                if (!TextUtils.isEmpty(pkg)) {
-                    packages.add(pkg);
-                }
-            }
-        }
-
-        return packages;
+        return getDataNetworkController().getDataServicePackages();
     }
 
     private void updateBroadcastEmergencyCallStateChangesAfterCarrierConfigChanged(
@@ -4804,28 +5024,26 @@
 
         boolean mIsVonrEnabledByCarrier =
                 config.getBoolean(CarrierConfigManager.KEY_VONR_ENABLED_BOOL);
-
-        String result = SubscriptionController.getInstance().getSubscriptionProperty(
-                getSubId(),
-                SubscriptionManager.NR_ADVANCED_CALLING_ENABLED);
+        boolean mDefaultVonr =
+                config.getBoolean(CarrierConfigManager.KEY_VONR_ON_BY_DEFAULT_BOOL);
 
         int setting = -1;
-        if (result != null) {
-            setting = Integer.parseInt(result);
+        SubscriptionInfoInternal subInfo = mSubscriptionManagerService
+                .getSubscriptionInfoInternal(getSubId());
+        if (subInfo != null) {
+            setting = subInfo.getNrAdvancedCallingEnabled();
         }
 
         logd("VoNR setting from telephony.db:"
                 + setting
                 + " ,vonr_enabled_bool:"
-                + mIsVonrEnabledByCarrier);
+                + mIsVonrEnabledByCarrier
+                + " ,vonr_on_by_default_bool:"
+                + mDefaultVonr);
 
-        if (!mIsVonrEnabledByCarrier) {
-            mCi.setVoNrEnabled(false, obtainMessage(EVENT_SET_VONR_ENABLED_DONE), null);
-        } else if (setting == 1 || setting == -1) {
-            mCi.setVoNrEnabled(true, obtainMessage(EVENT_SET_VONR_ENABLED_DONE), null);
-        } else if (setting == 0) {
-            mCi.setVoNrEnabled(false, obtainMessage(EVENT_SET_VONR_ENABLED_DONE), null);
-        }
+        boolean enbleVonr = mIsVonrEnabledByCarrier
+                && (setting == 1 || (setting == -1 && mDefaultVonr));
+        mCi.setVoNrEnabled(enbleVonr, obtainMessage(EVENT_SET_VONR_ENABLED_DONE), null);
     }
 
     private void updateCdmaRoamingSettingsAfterCarrierConfigChanged(PersistableBundle config) {
@@ -4887,4 +5105,49 @@
     public InboundSmsHandler getInboundSmsHandler(boolean is3gpp2) {
         return mIccSmsInterfaceManager.getInboundSmsHandler(is3gpp2);
     }
+
+    /**
+     * 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
+     * @return {@code true} if request is blocked due to FDN.
+     */
+    private boolean isRequestBlockedByFDN(SsData.RequestType requestType,
+            SsData.ServiceType serviceType) {
+        ArrayList<String> controlStrings = GsmMmiCode.getControlStrings(requestType, serviceType);
+        return FdnUtils.isSuppServiceRequestBlockedByFdn(mPhoneId, controlStrings, getCountryIso());
+    }
+
+    @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 a329771..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);
     }
 
     /**
@@ -625,9 +671,9 @@
         }
 
         if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
-            log("pdu: " + pdu +
-                "\n format=" + format +
-                "\n receivedIntent=" + receivedIntent);
+            log("pdu: " + IccUtils.bytesToHexString(pdu)
+                    + "\n format=" + format
+                    + "\n receivedIntent=" + receivedIntent);
         }
         mDispatchersController.injectSmsPdu(pdu, format, false /* isOverIms */,
                 result -> {
@@ -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 42fe7a7..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.
@@ -416,10 +551,43 @@
     }
 
     @Override
+    protected SmsMessageBase.SubmitPduBase getSubmitPdu(String scAddr, String destAddr,
+            String message, boolean statusReportRequested, SmsHeader smsHeader, int priority,
+            int validityPeriod, int messageRef) {
+        return SMSDispatcherUtil.getSubmitPdu(isCdmaMo(), scAddr, destAddr, message,
+                statusReportRequested, smsHeader, priority, validityPeriod, messageRef);
+    }
+
+    @Override
+    protected SmsMessageBase.SubmitPduBase getSubmitPdu(String scAddr, String destAddr,
+            int destPort, byte[] message, boolean statusReportRequested, int messageRef) {
+        return SMSDispatcherUtil.getSubmitPdu(isCdmaMo(), scAddr, destAddr, destPort, message,
+                statusReportRequested, messageRef);
+    }
+
+    @Override
     protected TextEncodingDetails calculateLength(CharSequence messageBody, boolean use7bitOnly) {
         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: "
@@ -489,6 +657,7 @@
 
     @VisibleForTesting
     public void fallbackToPstn(SmsTracker tracker) {
+        tracker.mMessageRef = nextMessageRef();
         mSmsDispatchersController.sendRetrySms(tracker);
     }
 
diff --git a/src/java/com/android/internal/telephony/InboundSmsHandler.java b/src/java/com/android/internal/telephony/InboundSmsHandler.java
index eeda1f9..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;
@@ -89,7 +87,6 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
@@ -116,38 +113,6 @@
     protected static final boolean DBG = true;
     protected static final boolean VDBG = false; // STOPSHIP if true, logs user data
 
-    /** Query projection for checking for duplicate message segments. */
-    private static final String[] PDU_DELETED_FLAG_PROJECTION = {
-            "pdu",
-            "deleted"
-    };
-
-    /** Mapping from DB COLUMN to PDU_SEQUENCE_PORT PROJECTION index */
-    private static final Map<Integer, Integer> PDU_DELETED_FLAG_PROJECTION_INDEX_MAPPING =
-            new HashMap<Integer, Integer>() {{
-            put(PDU_COLUMN, 0);
-            put(DELETED_FLAG_COLUMN, 1);
-            }};
-
-    /** Query projection for combining concatenated message segments. */
-    private static final String[] PDU_SEQUENCE_PORT_PROJECTION = {
-            "pdu",
-            "sequence",
-            "destination_port",
-            "display_originating_addr",
-            "date"
-    };
-
-    /** Mapping from DB COLUMN to PDU_SEQUENCE_PORT PROJECTION index */
-    private static final Map<Integer, Integer> PDU_SEQUENCE_PORT_PROJECTION_INDEX_MAPPING =
-            new HashMap<Integer, Integer>() {{
-                put(PDU_COLUMN, 0);
-                put(SEQUENCE_COLUMN, 1);
-                put(DESTINATION_PORT_COLUMN, 2);
-                put(DISPLAY_ADDRESS_COLUMN, 3);
-                put(DATE_COLUMN, 4);
-    }};
-
     public static final int PDU_COLUMN = 0;
     public static final int SEQUENCE_COLUMN = 1;
     public static final int DESTINATION_PORT_COLUMN = 2;
@@ -161,6 +126,34 @@
     public static final int DELETED_FLAG_COLUMN = 10;
     public static final int SUBID_COLUMN = 11;
 
+    /** Query projection for checking for duplicate message segments. */
+    private static final String[] PDU_DELETED_FLAG_PROJECTION = {
+            "pdu",
+            "deleted"
+    };
+
+    /** Mapping from DB COLUMN to PDU_SEQUENCE_PORT PROJECTION index */
+    private static final Map<Integer, Integer> PDU_DELETED_FLAG_PROJECTION_INDEX_MAPPING = Map.of(
+            PDU_COLUMN, 0,
+            DELETED_FLAG_COLUMN, 1);
+
+    /** Query projection for combining concatenated message segments. */
+    private static final String[] PDU_SEQUENCE_PORT_PROJECTION = {
+            "pdu",
+            "sequence",
+            "destination_port",
+            "display_originating_addr",
+            "date"
+    };
+
+    /** Mapping from DB COLUMN to PDU_SEQUENCE_PORT PROJECTION index */
+    private static final Map<Integer, Integer> PDU_SEQUENCE_PORT_PROJECTION_INDEX_MAPPING = Map.of(
+            PDU_COLUMN, 0,
+            SEQUENCE_COLUMN, 1,
+            DESTINATION_PORT_COLUMN, 2,
+            DISPLAY_ADDRESS_COLUMN, 3,
+            DATE_COLUMN, 4);
+
     public static final String SELECT_BY_ID = "_id=?";
 
     /** New SMS received as an AsyncResult. */
@@ -190,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;
 
@@ -294,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;
@@ -506,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:
@@ -545,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;
 
@@ -668,7 +662,6 @@
             }
         }
     }
-
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private void handleNewSms(AsyncResult ar) {
         if (ar.exception != null) {
@@ -679,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;
@@ -698,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 {
@@ -710,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);
@@ -731,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");
@@ -745,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).
@@ -780,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.
@@ -880,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>
      */
@@ -1164,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.");
@@ -1458,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);
@@ -1487,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 4e0f452..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;
@@ -486,20 +487,12 @@
         String countryIso = "";
         String countryIsoDebugInfo = "empty as default";
 
-        // For time zone detection we want the best geographical match we can get, which may differ
-        // from the countryIso.
-        String timeZoneCountryIso = null;
-        String timeZoneCountryIsoDebugInfo = null;
-
         if (!TextUtils.isEmpty(mOperatorNumeric)) {
             MccMnc mccMnc = MccMnc.fromOperatorNumeric(mOperatorNumeric);
             if (mccMnc != null) {
-                countryIso = MccTable.countryCodeForMcc(mccMnc.mcc);
+                countryIso = MccTable.geoCountryCodeForMccMnc(mccMnc);
                 countryIsoDebugInfo = "OperatorNumeric(" + mOperatorNumeric
-                        + "): MccTable.countryCodeForMcc(\"" + mccMnc.mcc + "\")";
-                timeZoneCountryIso = MccTable.geoCountryCodeForMccMnc(mccMnc);
-                timeZoneCountryIsoDebugInfo =
-                        "OperatorNumeric: MccTable.geoCountryCodeForMccMnc(" + mccMnc + ")";
+                        + "): MccTable.geoCountryCodeForMccMnc(\"" + mccMnc + "\")";
             } else {
                 loge("updateLocale: Can't get country from operator numeric. mOperatorNumeric = "
                         + mOperatorNumeric);
@@ -509,15 +502,19 @@
         // If for any reason we can't get country from operator numeric, try to get it from cell
         // info.
         if (TextUtils.isEmpty(countryIso)) {
+            // Find the most prevalent MCC from surrounding cell towers.
             String mcc = getMccFromCellInfo();
             if (mcc != null) {
                 countryIso = MccTable.countryCodeForMcc(mcc);
                 countryIsoDebugInfo = "CellInfo: MccTable.countryCodeForMcc(\"" + mcc + "\")";
 
+                // Some MCC+MNC combinations are known to be used in countries other than those
+                // that the MCC alone would suggest. Do a second pass of nearby cells that match
+                // the most frequently observed MCC to see if this could be one of those cases.
                 MccMnc mccMnc = getMccMncFromCellInfo(mcc);
                 if (mccMnc != null) {
-                    timeZoneCountryIso = MccTable.geoCountryCodeForMccMnc(mccMnc);
-                    timeZoneCountryIsoDebugInfo =
+                    countryIso = MccTable.geoCountryCodeForMccMnc(mccMnc);
+                    countryIsoDebugInfo =
                             "CellInfo: MccTable.geoCountryCodeForMccMnc(" + mccMnc + ")";
                 }
             }
@@ -526,8 +523,6 @@
         if (mCountryOverride != null) {
             countryIso = mCountryOverride;
             countryIsoDebugInfo = "mCountryOverride = \"" + mCountryOverride + "\"";
-            timeZoneCountryIso = countryIso;
-            timeZoneCountryIsoDebugInfo = countryIsoDebugInfo;
         }
 
         if (!mPhone.isRadioOn()) {
@@ -564,11 +559,13 @@
             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.
 
+        String timeZoneCountryIso = countryIso;
+        String timeZoneCountryIsoDebugInfo = countryIsoDebugInfo;
         boolean isTestMcc = false;
         if (!TextUtils.isEmpty(mOperatorNumeric)) {
             // For a test cell (MCC 001), the NitzStateMachine requires handleCountryDetected("") in
@@ -579,11 +576,6 @@
                 timeZoneCountryIsoDebugInfo = "Test cell: " + mOperatorNumeric;
             }
         }
-        if (timeZoneCountryIso == null) {
-            // After this timeZoneCountryIso may still be null.
-            timeZoneCountryIso = countryIso;
-            timeZoneCountryIsoDebugInfo = "Defaulted: " + countryIsoDebugInfo;
-        }
         log("updateLocale: timeZoneCountryIso = " + timeZoneCountryIso
                 + ", timeZoneCountryIsoDebugInfo = " + timeZoneCountryIsoDebugInfo);
 
diff --git a/src/java/com/android/internal/telephony/MessagingIndication.java b/src/java/com/android/internal/telephony/MessagingIndication.java
index 86e30b0..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,9 +49,9 @@
      */
     public void cdmaNewSms(int indicationType,
             android.hardware.radio.messaging.CdmaSmsMessage msg) {
-        mRil.processIndication(RIL.MESSAGING_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_MESSAGING, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESPONSE_CDMA_NEW_SMS);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_CDMA_NEW_SMS);
 
         SmsMessage sms = new SmsMessage(RILUtils.convertHalCdmaSmsMessage(msg));
         if (mRil.mCdmaSmsRegistrant != null) {
@@ -63,9 +65,9 @@
      * @param indicationType Type of radio indication
      */
     public void cdmaRuimSmsStorageFull(int indicationType) {
-        mRil.processIndication(RIL.MESSAGING_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_MESSAGING, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_CDMA_RUIM_SMS_STORAGE_FULL);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_CDMA_RUIM_SMS_STORAGE_FULL);
 
         if (mRil.mIccSmsFullRegistrant != null) {
             mRil.mIccSmsFullRegistrant.notifyRegistrant();
@@ -82,11 +84,11 @@
      *        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 (RIL.RILJ_LOGD) {
-            mRil.unsljLogvRet(RIL_UNSOL_RESPONSE_NEW_BROADCAST_SMS,
-                    IccUtils.bytesToHexString(data));
+        if (mRil.isLogOrTrace()) {
+            mRil.unsljLogvRet(
+                    RIL_UNSOL_RESPONSE_NEW_BROADCAST_SMS, IccUtils.bytesToHexString(data));
         }
 
         if (mRil.mGsmBroadcastSmsRegistrant != null) {
@@ -101,8 +103,8 @@
      *        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);
-        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESPONSE_NEW_SMS);
+        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);
         if (mRil.mGsmSmsRegistrant != null) {
@@ -117,9 +119,9 @@
      * @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 (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESPONSE_NEW_SMS_ON_SIM);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_NEW_SMS_ON_SIM);
 
         if (mRil.mSmsOnSimRegistrant != null) {
             mRil.mSmsOnSimRegistrant.notifyRegistrant(new AsyncResult(null, recordNumber, null));
@@ -133,9 +135,9 @@
      *        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 (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT);
 
         if (mRil.mSmsStatusRegistrant != null) {
             mRil.mSmsStatusRegistrant.notifyRegistrant(new AsyncResult(null, pdu, null));
@@ -149,9 +151,9 @@
      * @param indicationType Type of radio indication
      */
     public void simSmsStorageFull(int indicationType) {
-        mRil.processIndication(RIL.MESSAGING_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_MESSAGING, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_SIM_SMS_STORAGE_FULL);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_SIM_SMS_STORAGE_FULL);
 
         if (mRil.mIccSmsFullRegistrant != null) {
             mRil.mIccSmsFullRegistrant.notifyRegistrant();
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 5932f9e..dce65af 100644
--- a/src/java/com/android/internal/telephony/MissedIncomingCallSmsFilter.java
+++ b/src/java/com/android/internal/telephony/MissedIncomingCallSmsFilter.java
@@ -23,12 +23,14 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.PersistableBundle;
+import android.os.UserHandle;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.telephony.CarrierConfigManager;
 import android.telephony.Rlog;
 import android.telephony.SmsMessage;
+import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
 
 import java.time.Instant;
@@ -152,9 +154,6 @@
      * @return {@code true} if the SMS message has been processed as a missed incoming call SMS.
      */
     private boolean processSms(@NonNull SmsMessage message) {
-        long missedCallTime = 0;
-        String callerId = null;
-
         String[] smsPatterns = mCarrierConfig.getStringArray(CarrierConfigManager
                 .KEY_MISSED_INCOMING_CALL_SMS_PATTERN_STRING_ARRAY);
         if (smsPatterns == null || smsPatterns.length == 0) {
@@ -162,76 +161,91 @@
             return false;
         }
 
-        for (String smsPattern : smsPatterns) {
-            Pattern pattern;
-            try {
-                pattern = Pattern.compile(smsPattern, Pattern.DOTALL | Pattern.UNIX_LINES);
-            } catch (PatternSyntaxException e) {
-                Rlog.w(TAG, "Configuration error. Unexpected missed incoming call sms "
-                        + "pattern: " + smsPattern + ", e=" + e);
-                continue;
-            }
-
-            Matcher matcher = pattern.matcher(message.getMessageBody());
-            String year = null, month = null, day = null, hour = null, minute = null;
-            if (matcher.find()) {
-                try {
-                    month = matcher.group(SMS_MONTH_TAG);
-                    day = matcher.group(SMS_DAY_TAG);
-                    hour = matcher.group(SMS_HOUR_TAG);
-                    minute = matcher.group(SMS_MINUTE_TAG);
-                    if (VDBG) {
-                        Rlog.v(TAG, "month=" + month + ", day=" + day + ", hour=" + hour
-                                + ", minute=" + minute);
+        boolean result = false;
+        String[] missedCallMsgs = splitCalls(message.getMessageBody());
+        if (missedCallMsgs != null && missedCallMsgs.length > 0) {
+            for (String parsedMsg : missedCallMsgs) {
+                long missedCallTime = 0;
+                String callerId = null;
+                for (String smsPattern : smsPatterns) {
+                    Pattern pattern;
+                    try {
+                        pattern = Pattern.compile(smsPattern, Pattern.DOTALL | Pattern.UNIX_LINES);
+                    } catch (PatternSyntaxException e) {
+                        Rlog.w(TAG, "Configuration error. Unexpected missed incoming call sms "
+                                + "pattern: " + smsPattern + ", e=" + e);
+                        continue;
                     }
-                } catch (IllegalArgumentException e) {
-                    if (VDBG) {
-                        Rlog.v(TAG, "One of the critical date field is missing. Using the "
-                                + "current time for missed incoming call.");
-                    }
-                    missedCallTime = System.currentTimeMillis();
-                }
 
-                // Year is an optional field.
-                try {
-                    year = matcher.group(SMS_YEAR_TAG);
-                } catch (IllegalArgumentException e) {
-                    if (VDBG) Rlog.v(TAG, "Year is missing.");
-                }
-
-                try {
-                    if (missedCallTime == 0) {
-                        missedCallTime = getEpochTime(year, month, day, hour, minute);
-                        if (missedCallTime == 0) {
-                            Rlog.e(TAG, "Can't get the time. Use the current time.");
+                    Matcher matcher = pattern.matcher(parsedMsg);
+                    String year = null, month = null, day = null, hour = null, minute = null;
+                    if (matcher.find()) {
+                        try {
+                            month = matcher.group(SMS_MONTH_TAG);
+                            day = matcher.group(SMS_DAY_TAG);
+                            hour = matcher.group(SMS_HOUR_TAG);
+                            minute = matcher.group(SMS_MINUTE_TAG);
+                            if (VDBG) {
+                                Rlog.v(TAG, "month=" + month + ", day=" + day + ", hour=" + hour
+                                        + ", minute=" + minute);
+                            }
+                        } catch (IllegalArgumentException e) {
+                            if (VDBG) {
+                                Rlog.v(TAG, "One of the critical date field is missing. Using the "
+                                        + "current time for missed incoming call.");
+                            }
                             missedCallTime = System.currentTimeMillis();
                         }
+
+                        // Year is an optional field.
+                        try {
+                            year = matcher.group(SMS_YEAR_TAG);
+                        } catch (IllegalArgumentException e) {
+                            if (VDBG) Rlog.v(TAG, "Year is missing.");
+                        }
+
+                        try {
+                            if (missedCallTime == 0) {
+                                missedCallTime = getEpochTime(year, month, day, hour, minute);
+                                if (missedCallTime == 0) {
+                                    Rlog.e(TAG, "Can't get the time. Use the current time.");
+                                    missedCallTime = System.currentTimeMillis();
+                                }
+                            }
+
+                            if (VDBG) Rlog.v(TAG, "missedCallTime=" + missedCallTime);
+                        } catch (Exception e) {
+                            Rlog.e(TAG, "Can't get the time for missed incoming call");
+                        }
+
+                        try {
+                            callerId = matcher.group(SMS_CALLER_ID_TAG);
+                            if (VDBG) Rlog.v(TAG, "caller id=" + callerId);
+                        } catch (IllegalArgumentException e) {
+                            Rlog.d(TAG, "Caller id is not provided or can't be parsed.");
+                        }
+                        createMissedIncomingCallEvent(missedCallTime, callerId);
+                        result = true;
+                        break;
                     }
-
-                    if (VDBG) Rlog.v(TAG, "missedCallTime=" + missedCallTime);
-                } catch (Exception e) {
-                    Rlog.e(TAG, "Can't get the time for missed incoming call");
                 }
-
-                try {
-                    callerId = matcher.group(SMS_CALLER_ID_TAG);
-                    if (VDBG) Rlog.v(TAG, "caller id=" + callerId);
-                } catch (IllegalArgumentException e) {
-                    Rlog.d(TAG, "Caller id is not provided or can't be parsed.");
-                }
-                createMissedIncomingCallEvent(missedCallTime, callerId);
-                return true;
             }
         }
-
-        Rlog.d(TAG, "SMS did not match any missed incoming call SMS pattern.");
-        return false;
+        if (!result) {
+            Rlog.d(TAG, "SMS did not match any missed incoming call SMS pattern.");
+        }
+        return result;
     }
 
-    // Create phone account. The logic is copied from PhoneUtils.makePstnPhoneAccountHandle.
-    private static PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) {
-        return new PhoneAccountHandle(PSTN_CONNECTION_SERVICE_COMPONENT,
-                String.valueOf(phone.getFullIccSerialNumber()));
+    private String[] splitCalls(String messageBody) {
+        String[] messages = null;
+        if (messageBody != null) {
+            messages = messageBody.split("(\\n|\\s\\n)" + "(\\n|\\s\\n)");
+            Rlog.d(TAG,
+                    "splitTheMultipleCalls no of calls = " + ((messages != null) ? messages.length
+                            : 0));
+        }
+        return messages;
     }
 
     /**
@@ -260,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 8baa5fd..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,11 +46,11 @@
      */
     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);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_HARDWARE_CONFIG_CHANGED, response);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_HARDWARE_CONFIG_CHANGED, response);
 
         mRil.mHardwareConfigChangeRegistrants.notifyRegistrants(
                 new AsyncResult(null, response, null));
@@ -62,9 +64,9 @@
      *        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 (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_MODEM_RESTART, reason);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_MODEM_RESTART, reason);
 
         mRil.writeMetricsModemRestartEvent(reason);
         mRil.mModemResetRegistrants.notifyRegistrants(new AsyncResult(null, reason, null));
@@ -78,11 +80,11 @@
      */
     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);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_RADIO_CAPABILITY, response);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_RADIO_CAPABILITY, response);
 
         mRil.mPhoneRadioCapabilityChangedRegistrants.notifyRegistrants(
                 new AsyncResult(null, response, null));
@@ -94,12 +96,12 @@
      * @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 (RIL.RILJ_LOGD) {
-            mRil.unsljLogMore(RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED, "radioStateChanged: "
-                    + state);
+        if (mRil.isLogOrTrace()) {
+            mRil.unsljLogMore(
+                    RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED, "radioStateChanged: " + state);
         }
 
         mRil.setRadioState(state, false /* forceNotifyRegistrants */);
@@ -110,9 +112,9 @@
      * @param indicationType Type of radio indication
      */
     public void rilConnected(int indicationType) {
-        mRil.processIndication(RIL.MODEM_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_MODEM, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RIL_CONNECTED);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RIL_CONNECTED);
 
         // Initial conditions
         mRil.setRadioPower(false, null);
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 3f5c23c..0acae4b 100644
--- a/src/java/com/android/internal/telephony/MultiSimSettingController.java
+++ b/src/java/com/android/internal/telephony/MultiSimSettingController.java
@@ -33,10 +33,8 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.app.PendingIntent;
-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;
@@ -54,6 +52,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback;
+import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.util.ArrayUtils;
 
 import java.lang.annotation.Retention;
@@ -83,7 +83,6 @@
     private static final int EVENT_SUBSCRIPTION_INFO_CHANGED         = 4;
     private static final int EVENT_SUBSCRIPTION_GROUP_CHANGED        = 5;
     private static final int EVENT_DEFAULT_DATA_SUBSCRIPTION_CHANGED = 6;
-    private static final int EVENT_CARRIER_CONFIG_CHANGED            = 7;
     private static final int EVENT_MULTI_SIM_CONFIG_CHANGED          = 8;
     @VisibleForTesting
     public static final int EVENT_RADIO_STATE_CHANGED                = 9;
@@ -121,19 +120,15 @@
     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.
     @NonNull private List<Integer> mPrimarySubList = new ArrayList<>();
 
     /** 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.
@@ -158,19 +153,6 @@
 
     private static final String SETTING_USER_PREF_DATA_SUB = "user_preferred_data_sub";
 
-    private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
-                int phoneId = intent.getIntExtra(CarrierConfigManager.EXTRA_SLOT_INDEX,
-                        SubscriptionManager.INVALID_SIM_SLOT_INDEX);
-                int subId = intent.getIntExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
-                        SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-                notifyCarrierConfigChanged(phoneId, subId);
-            }
-        }
-    };
-
     private static class DataSettingsControllerCallback extends DataSettingsManagerCallback {
         private final Phone mPhone;
 
@@ -218,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);
             }
@@ -230,9 +212,9 @@
     }
 
     @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.
         final int phoneCount = ((TelephonyManager) mContext.getSystemService(
@@ -245,8 +227,12 @@
 
         mIsAskEverytimeSupportedForSms = mContext.getResources()
                 .getBoolean(com.android.internal.R.bool.config_sms_ask_every_time_support);
-        context.registerReceiver(mIntentReceiver, new IntentFilter(
-                CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
+
+        CarrierConfigManager ccm = mContext.getSystemService(CarrierConfigManager.class);
+        // Listener callback is executed on handler thread to directly handle config change
+        ccm.registerCarrierConfigChangeListener(this::post,
+                (slotIndex, subId, carrierId, specificCarrierId) ->
+                        onCarrierConfigChanged(slotIndex, subId));
     }
 
     /**
@@ -303,7 +289,7 @@
             case EVENT_USER_DATA_ENABLED: {
                 int subId = msg.arg1;
                 boolean enable = msg.arg2 != 0;
-                onUserDataEnabled(subId, enable);
+                onUserDataEnabled(subId, enable, true);
                 break;
             }
             case EVENT_ROAMING_DATA_ENABLED: {
@@ -325,11 +311,6 @@
             case EVENT_DEFAULT_DATA_SUBSCRIPTION_CHANGED:
                 onDefaultDataSettingChanged();
                 break;
-            case EVENT_CARRIER_CONFIG_CHANGED:
-                int phoneId = msg.arg1;
-                int subId = msg.arg2;
-                onCarrierConfigChanged(phoneId, subId);
-                break;
             case EVENT_MULTI_SIM_CONFIG_CHANGED:
                 int activeModems = (int) ((AsyncResult) msg.obj).result;
                 onMultiSimConfigChanged(activeModems);
@@ -352,17 +333,21 @@
      * If user is enabling a non-default non-opportunistic subscription, make it default
      * data subscription.
      */
-    protected void onUserDataEnabled(int subId, boolean enable) {
-        if (DBG) log("onUserDataEnabled");
+    private void onUserDataEnabled(int subId, boolean enable, boolean setDefaultData) {
+        if (DBG) log("[onUserDataEnabled] subId=" + subId + " enable=" + enable +
+        " setDefaultData=" + setDefaultData);
         // Make sure MOBILE_DATA of subscriptions in same group are synced.
         setUserDataEnabledForGroup(subId, enable);
 
+        SubscriptionInfo subInfo = mSubscriptionManagerService.getSubscriptionInfo(subId);
+        int defaultDataSubId = mSubscriptionManagerService.getDefaultDataSubId();
+
         // If user is enabling a non-default non-opportunistic subscription, make it default.
-        if (mSubController.getDefaultDataSubId() != subId && !mSubController.isOpportunistic(subId)
-                && enable && mSubController.isActiveSubId(subId)) {
-             android.provider.Settings.Global.putInt(mContext.getContentResolver(),
-                 SETTING_USER_PREF_DATA_SUB, subId);
-            mSubController.setDefaultDataSubId(subId);
+        if (defaultDataSubId != subId && subInfo != null && !subInfo.isOpportunistic() && enable
+                && subInfo.isActive() && setDefaultData) {
+            android.provider.Settings.Global.putInt(mContext.getContentResolver(),
+                    SETTING_USER_PREF_DATA_SUB, subId);
+            mSubscriptionManagerService.setDefaultDataSubId(subId);
         }
     }
 
@@ -373,8 +358,8 @@
         if (DBG) log("onRoamingDataEnabled");
         setRoamingDataEnabledForGroup(subId, enable);
 
-        // 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);
     }
 
     /**
@@ -419,14 +404,6 @@
         reEvaluateAll();
     }
 
-    /**
-     * Called when carrier config changes on any phone.
-     */
-    @VisibleForTesting
-    public void notifyCarrierConfigChanged(int phoneId, int subId) {
-        obtainMessage(EVENT_CARRIER_CONFIG_CHANGED, phoneId, subId).sendToTarget();
-    }
-
     private void onCarrierConfigChanged(int phoneId, int subId) {
         log("onCarrierConfigChanged phoneId " + phoneId + " subId " + subId);
         if (!SubscriptionManager.isValidPhoneId(phoneId)) {
@@ -438,14 +415,12 @@
         // being specified in it. So here we do additional check to make sur we don't miss the
         // subId.
         if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
-            int[] subIds = mSubController.getSubId(phoneId);
-            if (!ArrayUtils.isEmpty(subIds)) {
-                CarrierConfigManager cm = (CarrierConfigManager) mContext.getSystemService(
-                        mContext.CARRIER_CONFIG_SERVICE);
-                if (cm != null && cm.getConfigForSubId(subIds[0]) != null) {
-                    loge("onCarrierConfigChanged with invalid subId while subd "
-                            + subIds[0] + " is active and its config is loaded");
-                    subId = subIds[0];
+            subId = SubscriptionManager.getSubscriptionId(phoneId);
+            if (SubscriptionManager.isValidSubscriptionId(subId)) {
+                CarrierConfigManager cm = mContext.getSystemService(CarrierConfigManager.class);
+                if (cm != null && cm.getConfigForSubId(subId) != null) {
+                    loge("onCarrierConfigChanged with invalid subId while subId "
+                            + subId + " is active and its config is loaded");
                 }
             }
         }
@@ -459,7 +434,7 @@
      */
     @VisibleForTesting
     public boolean isCarrierConfigLoadedForAllSub() {
-        int[] activeSubIds = mSubController.getActiveSubIdList(false);
+        int[] activeSubIds = mSubscriptionManagerService.getActiveSubIdList(false);
         for (int activeSubId : activeSubIds) {
             boolean isLoaded = false;
             for (int configLoadedSub : mCarrierConfigLoadedSubIds) {
@@ -526,7 +501,7 @@
     private void onSubscriptionGroupChanged(ParcelUuid groupUuid) {
         if (DBG) log("onSubscriptionGroupChanged");
 
-        List<SubscriptionInfo> infoList = mSubController.getSubscriptionsInGroup(
+        List<SubscriptionInfo> infoList = mSubscriptionManagerService.getSubscriptionsInGroup(
                 groupUuid, mContext.getOpPackageName(), mContext.getAttributionTag());
         if (infoList == null || infoList.isEmpty()) return;
 
@@ -535,24 +510,36 @@
         int refSubId = infoList.get(0).getSubscriptionId();
         for (SubscriptionInfo info : infoList) {
             int subId = info.getSubscriptionId();
-            if (mSubController.isActiveSubId(subId) && !mSubController.isOpportunistic(subId)) {
+            if (info.isActive() && !info.isOpportunistic()) {
                 refSubId = subId;
                 break;
             }
         }
+
         if (DBG) log("refSubId is " + refSubId);
 
         boolean enable = false;
         try {
             enable = GlobalSettingsHelper.getBoolean(
                     mContext, Settings.Global.MOBILE_DATA, refSubId);
-            onUserDataEnabled(refSubId, enable);
         } catch (SettingNotFoundException exception) {
             //pass invalid refSubId to fetch the single-sim setting
             enable = GlobalSettingsHelper.getBoolean(
                     mContext, Settings.Global.MOBILE_DATA, INVALID_SUBSCRIPTION_ID, enable);
-            onUserDataEnabled(refSubId, enable);
         }
+        boolean setDefaultData = true;
+        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
+                // subscriptions which does not belong to this groupUuid
+                setDefaultData = false;
+                break;
+            }
+        }
+        onUserDataEnabled(refSubId, enable, setDefaultData);
 
         enable = false;
         try {
@@ -566,8 +553,7 @@
             onRoamingDataEnabled(refSubId, enable);
         }
 
-        // Sync settings in subscription database..
-        mSubController.syncGroupedSetting(refSubId);
+        mSubscriptionManagerService.syncGroupedSetting(refSubId);
     }
 
     /**
@@ -589,21 +575,24 @@
 
         if (!isReadyToReevaluate()) return;
 
-        List<SubscriptionInfo> activeSubInfos = mSubController
+        List<SubscriptionInfo> activeSubInfos = mSubscriptionManagerService
                 .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);
+            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);
-        if (DBG) log("[updateDefaultValues] change: " + change);
+        if (DBG) log("updateDefaultValues: change: " + change);
         if (change == PRIMARY_SUB_NO_CHANGE) return;
 
         // If there's only one primary subscription active, we trigger PREFERRED_PICK_DIALOG
@@ -615,33 +604,35 @@
                 || ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE))
                 .getActiveModemCount() == 1)) {
             int subId = mPrimarySubList.get(0);
-            if (DBG) log("[updateDefaultValues] to only primary sub " + subId);
-            mSubController.setDefaultDataSubId(subId);
-            mSubController.setDefaultVoiceSubId(subId);
-            mSubController.setDefaultSmsSubId(subId);
+            if (DBG) log("updateDefaultValues: to only primary sub " + subId);
+            mSubscriptionManagerService.setDefaultDataSubId(subId);
+            mSubscriptionManagerService.setDefaultVoiceSubId(subId);
+            mSubscriptionManagerService.setDefaultSmsSubId(subId);
             sendDefaultSubConfirmedNotification(subId);
             return;
         }
 
-        if (DBG) log("[updateDefaultValues] records: " + mPrimarySubList);
+        if (DBG) log("updateDefaultValues: records: " + mPrimarySubList);
+
+        boolean dataSelected, voiceSelected, smsSelected;
 
         // Update default data subscription.
-        if (DBG) log("[updateDefaultValues] Update default data subscription");
-        boolean dataSelected = updateDefaultValue(mPrimarySubList,
-                mSubController.getDefaultDataSubId(),
-                (newValue -> mSubController.setDefaultDataSubId(newValue)));
+        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");
-        boolean voiceSelected = updateDefaultValue(mPrimarySubList,
-                mSubController.getDefaultVoiceSubId(),
-                (newValue -> mSubController.setDefaultVoiceSubId(newValue)));
+        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");
-        boolean smsSelected = updateDefaultValue(mPrimarySubList,
-                mSubController.getDefaultSmsSubId(),
-                (newValue -> mSubController.setDefaultSmsSubId(newValue)),
+        if (DBG) log("updateDefaultValues: Update default sms subscription");
+        smsSelected = updateDefaultValue(mPrimarySubList,
+                mSubscriptionManagerService.getDefaultSmsSubId(),
+                mSubscriptionManagerService::setDefaultSmsSubId,
                 mIsAskEverytimeSupportedForSms);
 
         boolean autoFallbackEnabled = mContext.getResources().getBoolean(
@@ -693,7 +684,9 @@
             // any previous primary subscription becomes inactive, we consider it
             for (int subId : prevPrimarySubList) {
                 if (mPrimarySubList.contains(subId)) continue;
-                if (!mSubController.isActiveSubId(subId)) {
+                SubscriptionInfo subInfo = mSubscriptionManagerService.getSubscriptionInfo(subId);
+
+                if (subInfo == null || !subInfo.isActive()) {
                     for (int currentSubId : mPrimarySubList) {
                         if (areSubscriptionsInSameGroup(currentSubId, subId)) {
                             return PRIMARY_SUB_REMOVED_IN_GROUP;
@@ -701,10 +694,10 @@
                     }
                     return PRIMARY_SUB_REMOVED;
                 }
-                if (!mSubController.isOpportunistic(subId)) {
+                if (!subInfo.isOpportunistic()) {
                     // Should never happen.
-                    loge("[updatePrimarySubListAndGetChangeType]: missing active primary subId "
-                            + subId);
+                    loge("[updatePrimarySubListAndGetChangeType]: missing active primary "
+                            + "subId " + subId);
                 }
             }
             return PRIMARY_SUB_MARKED_OPPT;
@@ -729,6 +722,7 @@
         @TelephonyManager.DefaultSubscriptionSelectType
         int simSelectDialogType = getSimSelectDialogType(
                 change, dataSelected, voiceSelected, smsSelected);
+        log("sendSubChangeNotificationIfNeeded: simSelectDialogType=" + simSelectDialogType);
         SimCombinationWarningParams simCombinationParams = getSimCombinationWarningParams(change);
 
         if (simSelectDialogType != EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_NONE
@@ -760,6 +754,12 @@
             boolean voiceSelected, boolean smsSelected) {
         int dialogType = EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_NONE;
 
+        // Do not show preference selection dialog during SuW as there is fullscreen activity to
+        // choose preference.
+        if (Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.DEVICE_PROVISIONED, 0) == 0) {
+            return dialogType;
+        }
         // If a primary subscription is removed and only one is left active, ask user
         // for preferred sub selection if any default setting is not set.
         // If another primary subscription is added or default data is not selected, ask
@@ -768,12 +768,8 @@
                 && (!dataSelected || !smsSelected || !voiceSelected)) {
             dialogType = EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_ALL;
         } else if (mPrimarySubList.size() > 1 && (isUserVisibleChange(change)
-                || (change == PRIMARY_SUB_INITIALIZED && !dataSelected
-                && Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.DEVICE_PROVISIONED, 0) != 0))) {
+                || (change == PRIMARY_SUB_INITIALIZED && !dataSelected))) {
             // If change is SWAPPED_IN_GROUP or MARKED_OPPT, don't ask user again.
-            // In default DSDS devices, do not show data selection dialog during SuW as there is
-            // fullscreen activity to choose data preference.
             dialogType = EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_DATA;
         }
 
@@ -801,9 +797,12 @@
             // If a dual CDMA SIM combination warning is needed.
             if (phone != null && phone.isCdmaSubscriptionAppPresent()) {
                 cdmaPhoneCount++;
-                String simName = mSubController.getActiveSubscriptionInfo(
-                        subId, mContext.getOpPackageName(), mContext.getAttributionTag())
-                        .getDisplayName().toString();
+                String simName = null;
+                SubscriptionInfoInternal subInfo = mSubscriptionManagerService
+                        .getSubscriptionInfoInternal(subId);
+                if (subInfo != null) {
+                    simName = subInfo.getDisplayName();
+                }
                 if (TextUtils.isEmpty(simName)) {
                     // Fall back to carrier name.
                     simName = phone.getCarrierName();
@@ -828,23 +827,21 @@
     protected void disableDataForNonDefaultNonOpportunisticSubscriptions() {
         if (!isReadyToReevaluate()) return;
 
-        int defaultDataSub = mSubController.getDefaultDataSubId();
+        int defaultDataSub = mSubscriptionManagerService.getDefaultDataSubId();
 
         for (Phone phone : PhoneFactory.getPhones()) {
+            SubscriptionInfoInternal subInfo = mSubscriptionManagerService
+                    .getSubscriptionInfoInternal(phone.getSubId());
+            boolean isOpportunistic = subInfo != null && subInfo.isOpportunistic();
             if (phone.getSubId() != defaultDataSub
                     && SubscriptionManager.isValidSubscriptionId(phone.getSubId())
-                    && !mSubController.isOpportunistic(phone.getSubId())
+                    && !isOpportunistic
                     && phone.isUserDataEnabled()
                     && !areSubscriptionsInSameGroup(defaultDataSub, phone.getSubId())) {
                 log("setting data to false on " + phone.getSubId());
-                if (phone.isUsingNewDataStack()) {
-                    phone.getDataSettingsManager().setDataEnabled(
-                            TelephonyManager.DATA_ENABLED_REASON_USER, false,
-                            mContext.getOpPackageName());
-                } else {
-                    phone.getDataEnabledSettings().setDataEnabled(
-                            TelephonyManager.DATA_ENABLED_REASON_USER, false);
-                }
+                phone.getDataSettingsManager().setDataEnabled(
+                        TelephonyManager.DATA_ENABLED_REASON_USER, false,
+                        mContext.getOpPackageName());
             }
         }
     }
@@ -854,9 +851,13 @@
                 || !SubscriptionManager.isUsableSubscriptionId(subId2)) return false;
         if (subId1 == subId2) return true;
 
-        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());
     }
 
     /**
@@ -865,28 +866,30 @@
      */
     protected void setUserDataEnabledForGroup(int subId, boolean enable) {
         log("setUserDataEnabledForGroup subId " + subId + " enable " + enable);
-        List<SubscriptionInfo> infoList = mSubController.getSubscriptionsInGroup(
-                mSubController.getGroupUuid(subId), mContext.getOpPackageName(),
-                mContext.getAttributionTag());
+        List<SubscriptionInfo> infoList = null;
+        SubscriptionInfoInternal subInfo = mSubscriptionManagerService
+                .getSubscriptionInfoInternal(subId);
+        if (subInfo != null && !subInfo.getGroupUuid().isEmpty()) {
+            infoList = mSubscriptionManagerService.getSubscriptionsInGroup(
+                    ParcelUuid.fromString(subInfo.getGroupUuid()), mContext.getOpPackageName(),
+                    mContext.getAttributionTag());
+        }
 
         if (infoList == null) return;
 
         for (SubscriptionInfo info : infoList) {
             int currentSubId = info.getSubscriptionId();
             // TODO: simplify when setUserDataEnabled becomes singleton
-            if (mSubController.isActiveSubId(currentSubId)) {
+            if (info.isActive()) {
                 // For active subscription, call setUserDataEnabled through DataSettingsManager.
-                Phone 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) {
-                    if (phone.isUsingNewDataStack()) {
-                        phone.getDataSettingsManager().setDataEnabled(
-                                TelephonyManager.DATA_ENABLED_REASON_USER, enable,
-                                mContext.getOpPackageName());
-                    } else {
-                        phone.getDataEnabledSettings().setUserDataEnabled(enable, false);
-                    }
+                    phone.getDataSettingsManager().setDataEnabled(
+                            TelephonyManager.DATA_ENABLED_REASON_USER, enable,
+                            mContext.getOpPackageName());
                 }
             } else {
                 // For inactive subscription, directly write into global settings.
@@ -901,11 +904,12 @@
      * are synced.
      */
     private void setRoamingDataEnabledForGroup(int subId, boolean enable) {
-        SubscriptionController subController = SubscriptionController.getInstance();
-        List<SubscriptionInfo> infoList = subController.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) {
@@ -958,16 +962,16 @@
     // 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 = 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;
 
         for (SubscriptionInfo info : opptSubList) {
-            if (info.isGroupDisabled() && mSubController.isActiveSubId(info.getSubscriptionId())) {
-                log("[deactivateGroupedOpptSubIfNeeded] "
+            if (info.isGroupDisabled() && info.isActive()) {
+                log("deactivateGroupedOpportunisticSubscriptionIfNeeded: "
                         + "Deactivating grouped opportunistic subscription "
                         + info.getSubscriptionId());
                 deactivateSubscription(info);
@@ -997,47 +1001,55 @@
     // would be selected as preferred voice/data/sms SIM.
     private void updateUserPreferences(List<Integer> primarySubList, boolean dataSelected,
             boolean voiceSelected, boolean smsSelected) {
-        // In Single SIM case or if there are no activated subs available, no need to update. EXIT.
-        if ((primarySubList.isEmpty()) || (mSubController.getActiveSubInfoCountMax() == 1)) return;
+        // 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 = 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());
+        if (DBG) {
+            log("updateUserPreferences: after dds = "
+                    + mSubscriptionManagerService.getDefaultDataSubId() + " voice = "
+                    + mSubscriptionManagerService.getDefaultVoiceSubId() + " sms = "
+                    + mSubscriptionManagerService.getDefaultSmsSubId());
+        }
     }
 
     private int getUserPrefDataSubIdFromDB() {
@@ -1061,10 +1073,8 @@
         // existing phone instance.
         Phone[] phones = PhoneFactory.getPhones();
         for (int i = mCallbacksCount; i < phones.length; i++) {
-            if (phones[i].isUsingNewDataStack()) {
-                phones[i].getDataSettingsManager().registerCallback(
-                        new DataSettingsControllerCallback(phones[i], this::post));
-            }
+            phones[i].getDataSettingsManager().registerCallback(
+                    new DataSettingsControllerCallback(phones[i], this::post));
         }
         mCallbacksCount = phones.length;
     }
diff --git a/src/java/com/android/internal/telephony/NetworkIndication.java b/src/java/com/android/internal/telephony/NetworkIndication.java
index 17d906e..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,11 +97,11 @@
      * @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};
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CDMA_PRL_CHANGED, response);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_CDMA_PRL_CHANGED, response);
 
         mRil.mCdmaPrlChangedRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
     }
@@ -109,9 +113,9 @@
      */
     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 (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CELL_INFO_LIST, response);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_CELL_INFO_LIST, response);
         mRil.mRilCellInfoListRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
     }
 
@@ -122,11 +126,11 @@
      */
     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);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_LCEDATA_RECV, response);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_LCEDATA_RECV, response);
 
         if (mRil.mLceInfoRegistrants != null) {
             mRil.mLceInfoRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
@@ -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)
@@ -178,7 +189,7 @@
             mRil.riljLoge("Invalid PhysicalChannelConfig " + iae);
             return;
         }
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_PHYSICAL_CHANNEL_CONFIG, response);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_PHYSICAL_CHANNEL_CONFIG, response);
 
         mRil.mPhysicalChannelConfigurationRegistrants.notifyRegistrants(
                 new AsyncResult(null, response, null));
@@ -191,13 +202,13 @@
      */
     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);
 
         SignalStrength ss = mRil.fixupSignalStrength10(ssInitial);
         // Note this is set to "verbose" because it happens frequently
-        if (RIL.RILJ_LOGV) mRil.unsljLogvRet(RIL_UNSOL_SIGNAL_STRENGTH, ss);
+        if (mRil.isLogvOrTrace()) mRil.unsljLogvRet(RIL_UNSOL_SIGNAL_STRENGTH, ss);
 
         if (mRil.mSignalStrengthRegistrant != null) {
             mRil.mSignalStrengthRegistrant.notifyRegistrant(new AsyncResult(null, ss, null));
@@ -209,9 +220,9 @@
      * @param indicationType Type of radio indication
      */
     public void imsNetworkStateChanged(int indicationType) {
-        mRil.processIndication(RIL.NETWORK_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_NETWORK, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESPONSE_IMS_NETWORK_STATE_CHANGED);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_IMS_NETWORK_STATE_CHANGED);
 
         mRil.mImsNetworkStateChangedRegistrants.notifyRegistrants();
     }
@@ -223,11 +234,11 @@
      */
     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);
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_NETWORK_SCAN_RESULT, nsr);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_NETWORK_SCAN_RESULT, nsr);
         mRil.mRilNetworkScanResultRegistrants.notifyRegistrants(new AsyncResult(null, nsr, null));
     }
 
@@ -236,9 +247,9 @@
      * @param indicationType Type of radio indication
      */
     public void networkStateChanged(int indicationType) {
-        mRil.processIndication(RIL.NETWORK_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_NETWORK, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESPONSE_NETWORK_STATE_CHANGED);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_NETWORK_STATE_CHANGED);
 
         mRil.mNetworkStateRegistrants.notifyRegistrants();
     }
@@ -256,9 +267,9 @@
      */
     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 (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_NITZ_TIME_RECEIVED, nitzTime);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_NITZ_TIME_RECEIVED, nitzTime);
 
         // Ignore the NITZ if receivedTimeMs or ageMs is not a valid time.
         // e.g. receivedTimeMs is non-positive, ageMs is negative or greater than receivedTimeMs.
@@ -280,7 +291,7 @@
         boolean ignoreNitz = TelephonyProperties.ignore_nitz().orElse(false);
 
         if (ignoreNitz) {
-            if (RIL.RILJ_LOGD) mRil.riljLog("ignoring UNSOL_NITZ_TIME_RECEIVED");
+            if (mRil.isLogOrTrace()) mRil.riljLog("ignoring UNSOL_NITZ_TIME_RECEIVED");
         } else {
             if (mRil.mNITZTimeRegistrant != null) {
                 mRil.mNITZTimeRegistrant.notifyRegistrant(new AsyncResult(null, result, null));
@@ -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,9 +348,9 @@
      * @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 (RIL.RILJ_LOGD) mRil.unsljLogvRet(RIL_UNSOL_RESTRICTED_STATE_CHANGED, state);
+        if (mRil.isLogOrTrace()) mRil.unsljLogvRet(RIL_UNSOL_RESTRICTED_STATE_CHANGED, state);
 
         if (mRil.mRestrictedStateRegistrant != null) {
             mRil.mRestrictedStateRegistrant.notifyRegistrant(new AsyncResult(null, state, null));
@@ -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;
@@ -353,7 +373,9 @@
         notification.type = suppSvcNotification.type;
         notification.number = suppSvcNotification.number;
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_SUPP_SVC_NOTIFICATION, notification);
+        if (mRil.isLogOrTrace()) {
+            mRil.unsljLogRet(RIL_UNSOL_SUPP_SVC_NOTIFICATION, notification);
+        }
 
         if (mRil.mSsnRegistrant != null) {
             mRil.mSsnRegistrant.notifyRegistrant(new AsyncResult(null, notification, null));
@@ -366,16 +388,37 @@
      * @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};
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_VOICE_RADIO_TECH_CHANGED, response);
+        if (mRil.isLogOrTrace()) {
+            mRil.unsljLogRet(RIL_UNSOL_VOICE_RADIO_TECH_CHANGED, response);
+        }
 
         mRil.mVoiceRadioTechChangedRegistrants.notifyRegistrants(
                 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/NetworkRegistrationManager.java b/src/java/com/android/internal/telephony/NetworkRegistrationManager.java
index 3535678..91cd4ec 100644
--- a/src/java/com/android/internal/telephony/NetworkRegistrationManager.java
+++ b/src/java/com/android/internal/telephony/NetworkRegistrationManager.java
@@ -16,13 +16,10 @@
 
 package com.android.internal.telephony;
 
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.IBinder;
@@ -30,7 +27,6 @@
 import android.os.PersistableBundle;
 import android.os.RegistrantList;
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.AccessNetworkConstants.TransportType;
 import android.telephony.CarrierConfigManager;
@@ -58,9 +54,6 @@
     private final int mTransportType;
 
     private final Phone mPhone;
-
-    private final CarrierConfigManager mCarrierConfigManager;
-
     // Registrants who listens registration state change callback from this class.
     private final RegistrantList mRegStateChangeRegistrants = new RegistrantList();
 
@@ -72,22 +65,6 @@
 
     private NetworkServiceConnection mServiceConnection;
 
-    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(action)
-                    && mPhone.getPhoneId() == intent.getIntExtra(
-                    CarrierConfigManager.EXTRA_SLOT_INDEX, 0)) {
-                // 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.
-                logd("Carrier config changed. Try to bind network service.");
-                sendEmptyMessage(EVENT_BIND_NETWORK_SERVICE);
-            }
-        }
-    };
-
     public NetworkRegistrationManager(@TransportType int transportType, Phone phone) {
         mTransportType = transportType;
         mPhone = phone;
@@ -96,19 +73,20 @@
                 ? "C" : "I") + "-" + mPhone.getPhoneId();
         mTag = "NRM" + tagSuffix;
 
-        mCarrierConfigManager = (CarrierConfigManager) phone.getContext().getSystemService(
-                Context.CARRIER_CONFIG_SERVICE);
+        CarrierConfigManager ccm = phone.getContext().getSystemService(CarrierConfigManager.class);
+        // Callback directly calls rebindService and should be executed in handler thread
+        ccm.registerCarrierConfigChangeListener(
+                this::post,
+                (slotIndex, subId, carrierId, specificCarrierId) -> {
+                    if (slotIndex == phone.getPhoneId()) {
+                        // We should wait for carrier config changed event because the target
+                        // binding package name can come from the carrier config. Note that
+                        // we still get this event even when SIM is absent.
+                        logd("Carrier config changed. Try to bind network service.");
+                        rebindService();
+                    }
+                });
 
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        try {
-            Context contextAsUser = phone.getContext().createPackageContextAsUser(
-                phone.getContext().getPackageName(), 0, UserHandle.ALL);
-            contextAsUser.registerReceiver(mBroadcastReceiver, intentFilter,
-                null /* broadcastPermission */, null);
-        } catch (PackageManager.NameNotFoundException e) {
-            loge("Package name not found: " + e.getMessage());
-        }
         PhoneConfigurationManager.registerForMultiSimConfigChange(
                 this, EVENT_BIND_NETWORK_SERVICE, null);
 
@@ -333,9 +311,10 @@
         // Read package name from resource overlay
         packageName = mPhone.getContext().getResources().getString(resourceId);
 
-        PersistableBundle b = mCarrierConfigManager.getConfigForSubId(mPhone.getSubId());
-
-        if (b != null && !TextUtils.isEmpty(b.getString(carrierConfig))) {
+        PersistableBundle b =
+                CarrierConfigManager.getCarrierConfigSubset(
+                        mPhone.getContext(), mPhone.getSubId(), carrierConfig);
+        if (!b.isEmpty() && !TextUtils.isEmpty(b.getString(carrierConfig))) {
             // If carrier config overrides it, use the one from carrier config
             packageName = b.getString(carrierConfig, packageName);
         }
@@ -367,15 +346,17 @@
         // Read class name from resource overlay
         className = mPhone.getContext().getResources().getString(resourceId);
 
-        PersistableBundle b = mCarrierConfigManager.getConfigForSubId(mPhone.getSubId());
-
-        if (b != null && !TextUtils.isEmpty(b.getString(carrierConfig))) {
+        PersistableBundle b =
+                CarrierConfigManager.getCarrierConfigSubset(
+                        mPhone.getContext(), mPhone.getSubId(), carrierConfig);
+        if (!b.isEmpty() && !TextUtils.isEmpty(b.getString(carrierConfig))) {
             // If carrier config overrides it, use the one from carrier config
             className = b.getString(carrierConfig, className);
         }
 
         return className;
     }
+
     private void logd(String msg) {
         Rlog.d(mTag, msg);
     }
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 ea68cba..7567566 100644
--- a/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java
+++ b/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java
@@ -42,6 +42,8 @@
 import android.telephony.TelephonyScanManager;
 import android.util.Log;
 
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
+
 import java.util.Collection;
 import java.util.List;
 import java.util.Set;
@@ -195,7 +197,7 @@
     public static Set<String> getAllowedMccMncsForLocationRestrictedScan(Context context) {
         final long token = Binder.clearCallingIdentity();
         try {
-            return SubscriptionController.getInstance()
+            return SubscriptionManagerService.getInstance()
                     .getAvailableSubscriptionInfoList(context.getOpPackageName(),
                             context.getAttributionTag()).stream()
                     .flatMap(NetworkScanRequestTracker::getAllowableMccMncsFromSubscriptionInfo)
@@ -472,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);
             }
         }
 
@@ -514,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 {
@@ -526,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 2260d34..beebf22 100644
--- a/src/java/com/android/internal/telephony/NetworkTypeController.java
+++ b/src/java/com/android/internal/telephony/NetworkTypeController.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -28,20 +30,16 @@
 import android.telephony.Annotation;
 import android.telephony.CarrierConfigManager;
 import android.telephony.NetworkRegistrationInfo;
-import android.telephony.PcoData;
 import android.telephony.PhysicalChannelConfig;
 import android.telephony.ServiceState;
-import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager;
-import android.telephony.data.ApnSetting;
 import android.telephony.data.DataCallResponse;
 import android.telephony.data.DataCallResponse.LinkStatus;
 import android.text.TextUtils;
 
 import com.android.internal.telephony.data.DataNetworkController.DataNetworkControllerCallback;
-import com.android.internal.telephony.dataconnection.DataConnection;
-import com.android.internal.telephony.dataconnection.DcTracker;
+import com.android.internal.telephony.data.DataUtils;
 import com.android.internal.telephony.util.ArrayUtils;
 import com.android.internal.util.IState;
 import com.android.internal.util.IndentingPrintWriter;
@@ -55,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;
@@ -86,31 +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_PCO_DATA_CHANGED = 14;
-    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";
@@ -121,26 +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_PCO_DATA_CHANGED] = "EVENT_PCO_DATA_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;
@@ -148,19 +140,33 @@
         }
     };
 
-    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;
-    private boolean mIsTimerResetEnabledForLegacyStateRRCIdle;
+    private boolean mIsTimerResetEnabledForLegacyStateRrcIdle;
     private int mLtePlusThresholdBandwidth;
     private int mNrAdvancedThresholdBandwidth;
-    private int[] mAdditionalNrAdvancedBandsList;
-    private String mPrimaryTimerState;
-    private String mSecondaryTimerState;
-    private String mPreviousState;
+    private boolean mIncludeLteForNrAdvancedThresholdBandwidth;
+    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 +175,13 @@
     private boolean mEnableNrAdvancedWhileRoaming = true;
     private boolean mIsDeviceIdleMode = false;
 
+    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,13 +194,22 @@
         mDisplayInfoController = displayInfoController;
         mOverrideNetworkType = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
         mIsPhysicalChannelConfigOn = true;
-        addState(mDefaultState);
-        addState(mLegacyState, mDefaultState);
-        addState(mIdleState, mDefaultState);
-        addState(mLteConnectedState, mDefaultState);
-        addState(mNrConnectedState, mDefaultState);
-        setInitialState(mDefaultState);
+        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);
     }
 
@@ -200,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;
     }
 
@@ -214,133 +247,101 @@
                 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);
-        if (!mPhone.isUsingNewDataStack()) {
-            mPhone.mCi.registerForPcoData(getHandler(), EVENT_PCO_DATA_CHANGED, null);
-        }
+        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);
-        if (!mPhone.isUsingNewDataStack()) {
-            mPhone.mCi.unregisterForPcoData(getHandler());
+        CarrierConfigManager ccm = mPhone.getContext().getSystemService(CarrierConfigManager.class);
+        if (mCarrierConfigChangeListener != null) {
+            ccm.unregisterCarrierConfigChangeListener(mCarrierConfigChangeListener);
         }
     }
 
     private void parseCarrierConfigs() {
-        String nrIconConfiguration = CarrierConfigManager.getDefaultConfig().getString(
-                CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING);
-        String overrideTimerRule = CarrierConfigManager.getDefaultConfig().getString(
-                CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING);
-        String overrideSecondaryTimerRule = CarrierConfigManager.getDefaultConfig().getString(
-                CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING);
-        mLteEnhancedPattern = CarrierConfigManager.getDefaultConfig().getString(
-                CarrierConfigManager.KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING);
-        mIsTimerResetEnabledForLegacyStateRRCIdle =
-                CarrierConfigManager.getDefaultConfig().getBoolean(
-                        CarrierConfigManager.KEY_NR_TIMERS_RESET_IF_NON_ENDC_AND_RRC_IDLE_BOOL);
-        mLtePlusThresholdBandwidth = CarrierConfigManager.getDefaultConfig().getInt(
-                CarrierConfigManager.KEY_LTE_PLUS_THRESHOLD_BANDWIDTH_KHZ_INT);
-        mNrAdvancedThresholdBandwidth = CarrierConfigManager.getDefaultConfig().getInt(
-                CarrierConfigManager.KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT);
-        mEnableNrAdvancedWhileRoaming = CarrierConfigManager.getDefaultConfig().getBoolean(
-                CarrierConfigManager.KEY_ENABLE_NR_ADVANCED_WHILE_ROAMING_BOOL);
-
-        CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
-                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        PersistableBundle config = CarrierConfigManager.getDefaultConfig();
+        CarrierConfigManager configManager =
+                mPhone.getContext().getSystemService(CarrierConfigManager.class);
         if (configManager != null) {
             PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId());
             if (b != null) {
-                if (b.getString(CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING) != null) {
-                    nrIconConfiguration = b.getString(
-                            CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING);
-                }
-                if (b.getString(CarrierConfigManager
-                        .KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING) != null) {
-                    overrideTimerRule = b.getString(
-                            CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING);
-                }
-                if (b.getString(CarrierConfigManager
-                        .KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING) != null) {
-                    overrideSecondaryTimerRule = b.getString(
-                            CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING);
-                }
-                if (b.getString(CarrierConfigManager
-                        .KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING) != null) {
-                    mLteEnhancedPattern = b.getString(
-                            CarrierConfigManager.KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING);
-                }
-                mIsTimerResetEnabledForLegacyStateRRCIdle = b.getBoolean(
-                        CarrierConfigManager.KEY_NR_TIMERS_RESET_IF_NON_ENDC_AND_RRC_IDLE_BOOL);
-                mLtePlusThresholdBandwidth = b.getInt(
-                        CarrierConfigManager.KEY_LTE_PLUS_THRESHOLD_BANDWIDTH_KHZ_INT,
-                        mLtePlusThresholdBandwidth);
-                mNrAdvancedThresholdBandwidth = b.getInt(
-                        CarrierConfigManager.KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT,
-                        mNrAdvancedThresholdBandwidth);
-                mAdditionalNrAdvancedBandsList = b.getIntArray(
-                        CarrierConfigManager.KEY_ADDITIONAL_NR_ADVANCED_BANDS_INT_ARRAY);
-                mNrAdvancedCapablePcoId = b.getInt(
-                        CarrierConfigManager.KEY_NR_ADVANCED_CAPABLE_PCO_ID_INT);
-                if (mNrAdvancedCapablePcoId > 0 && mPhone.isUsingNewDataStack()) {
-                    mPhone.getDataNetworkController().registerDataNetworkControllerCallback(
-                            new DataNetworkControllerCallback(getHandler()::post) {
-                                @Override
-                                public void onNrAdvancedCapableByPcoChanged(
-                                        boolean nrAdvancedCapable) {
-                                    log("mIsNrAdvancedAllowedByPco=" + nrAdvancedCapable);
-                                    mIsNrAdvancedAllowedByPco = nrAdvancedCapable;
-                                    sendMessage(EVENT_UPDATE_NR_ADVANCED_STATE);
-                                }
-                            });
-                }
-                mEnableNrAdvancedWhileRoaming = b.getBoolean(
-                        CarrierConfigManager.KEY_ENABLE_NR_ADVANCED_WHILE_ROAMING_BOOL);
-                mIsUsingUserDataForRrcDetection = b.getBoolean(
-                        CarrierConfigManager.KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL);
-                if (!mIsPhysicalChannelConfig16Supported || mIsUsingUserDataForRrcDetection) {
-                    if (mPhone.isUsingNewDataStack()) {
-                        mPhone.getDataNetworkController().registerDataNetworkControllerCallback(
-                                new DataNetworkControllerCallback(getHandler()::post) {
-                                    @Override
-                                    public void onPhysicalLinkStatusChanged(
-                                            @LinkStatus int status) {
-                                        sendMessage(obtainMessage(
-                                                EVENT_PHYSICAL_LINK_STATUS_CHANGED,
-                                                new AsyncResult(null, status, null)));
-                                    }});
-                    } else {
-                        mPhone.getDcTracker(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                                .registerForPhysicalLinkStatusChanged(getHandler(),
-                                        EVENT_PHYSICAL_LINK_STATUS_CHANGED);
-                    }
-                }
+                config = b;
             }
         }
+        mLteEnhancedPattern = config.getString(
+                CarrierConfigManager.KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING);
+        mIsTimerResetEnabledForLegacyStateRrcIdle = config.getBoolean(
+                CarrierConfigManager.KEY_NR_TIMERS_RESET_IF_NON_ENDC_AND_RRC_IDLE_BOOL);
+        mLtePlusThresholdBandwidth = config.getInt(
+                CarrierConfigManager.KEY_LTE_PLUS_THRESHOLD_BANDWIDTH_KHZ_INT);
+        mNrAdvancedThresholdBandwidth = config.getInt(
+                CarrierConfigManager.KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT);
+        mIncludeLteForNrAdvancedThresholdBandwidth = config.getBoolean(
+                CarrierConfigManager.KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL);
+        mEnableNrAdvancedWhileRoaming = config.getBoolean(
+                CarrierConfigManager.KEY_ENABLE_NR_ADVANCED_WHILE_ROAMING_BOOL);
+        mAdditionalNrAdvancedBandsList = config.getIntArray(
+                CarrierConfigManager.KEY_ADDITIONAL_NR_ADVANCED_BANDS_INT_ARRAY);
+        mNrAdvancedCapablePcoId = config.getInt(
+                CarrierConfigManager.KEY_NR_ADVANCED_CAPABLE_PCO_ID_INT);
+        if (mNrAdvancedCapablePcoId > 0 && mNrAdvancedCapableByPcoChangedCallback == null) {
+            mNrAdvancedCapableByPcoChangedCallback =
+                    new DataNetworkControllerCallback(getHandler()::post) {
+                        @Override
+                        public void onNrAdvancedCapableByPcoChanged(boolean nrAdvancedCapable) {
+                            log("mIsNrAdvancedAllowedByPco=" + nrAdvancedCapable);
+                            mIsNrAdvancedAllowedByPco = nrAdvancedCapable;
+                            sendMessage(EVENT_UPDATE);
+                        }
+                    };
+            mPhone.getDataNetworkController().registerDataNetworkControllerCallback(
+                    mNrAdvancedCapableByPcoChangedCallback);
+        } else if (mNrAdvancedCapablePcoId == 0 && mNrAdvancedCapableByPcoChangedCallback != null) {
+            mPhone.getDataNetworkController().unregisterDataNetworkControllerCallback(
+                    mNrAdvancedCapableByPcoChangedCallback);
+            mNrAdvancedCapableByPcoChangedCallback = null;
+        }
+        mIsUsingUserDataForRrcDetection = config.getBoolean(
+                CarrierConfigManager.KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL);
+        if (!isUsingPhysicalChannelConfigForRrcDetection()) {
+            if (mNrPhysicalLinkStatusChangedCallback == null) {
+                mNrPhysicalLinkStatusChangedCallback =
+                        new DataNetworkControllerCallback(getHandler()::post) {
+                            @Override
+                            public void onPhysicalLinkStatusChanged(@LinkStatus int status) {
+                                sendMessage(obtainMessage(EVENT_PHYSICAL_LINK_STATUS_CHANGED,
+                                        new AsyncResult(null, status, null)));
+                            }
+                        };
+                mPhone.getDataNetworkController().registerDataNetworkControllerCallback(
+                        mNrPhysicalLinkStatusChangedCallback);
+            }
+        } else if (mNrPhysicalLinkStatusChangedCallback != null) {
+            mPhone.getDataNetworkController().unregisterDataNetworkControllerCallback(
+                    mNrPhysicalLinkStatusChangedCallback);
+            mNrPhysicalLinkStatusChangedCallback = null;
+        }
+        String nrIconConfiguration = config.getString(
+                CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING);
+        String overrideTimerRule = config.getString(
+                CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING);
+        String overrideSecondaryTimerRule = config.getString(
+                CarrierConfigManager.KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING);
         createTimerRules(nrIconConfiguration, overrideTimerRule, overrideSecondaryTimerRule);
     }
 
@@ -349,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;
@@ -376,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;
@@ -402,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);
@@ -444,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
@@ -475,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);
@@ -505,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()) {
@@ -521,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()) {
@@ -542,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;
@@ -555,26 +554,15 @@
                     break;
                 case EVENT_INITIALIZE:
                     // The reason that we do it here is because some of the works below requires
-                    // other modules (e.g. DcTracker, ServiceStateTracker), which is not created
-                    // yet when NetworkTypeController is created.
+                    // other modules (e.g. DataNetworkController, ServiceStateTracker), which is not
+                    // created yet when NetworkTypeController is created.
                     registerForAllEvents();
                     parseCarrierConfigs();
                     break;
-                case EVENT_DATA_RAT_CHANGED:
-                case EVENT_NR_STATE_CHANGED:
-                case EVENT_NR_FREQUENCY_CHANGED:
-                case EVENT_PCO_DATA_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;
@@ -616,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();
@@ -635,8 +637,6 @@
         }
     }
 
-    private final DefaultState mDefaultState = new DefaultState();
-
     /**
      * Device does not have NR available, due to any of the below reasons:
      * <ul>
@@ -664,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);
@@ -681,35 +689,22 @@
                     }
                     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()) {
+                        if (mIsTimerResetEnabledForLegacyStateRrcIdle && !isPhysicalLinkActive()) {
                             if (DBG) log("Reset timers since timer reset is enabled for RRC idle.");
                             resetAllTimers();
                         }
                     }
-                    // Update in case of LTE/LTE+ switch
-                    updateOverrideNetworkType();
                     break;
                 case EVENT_PHYSICAL_LINK_STATUS_CHANGED:
                     AsyncResult ar = (AsyncResult) msg.obj;
                     mPhysicalLinkStatus = (int) ar.result;
-                    if (mIsTimerResetEnabledForLegacyStateRRCIdle && !isPhysicalLinkActive()) {
+                    if (mIsTimerResetEnabledForLegacyStateRrcIdle && !isPhysicalLinkActive()) {
                         if (DBG) log("Reset timers since timer reset is enabled for RRC idle.");
                         resetAllTimers();
                         updateOverrideNetworkType();
@@ -751,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:
@@ -835,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:
@@ -904,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);
@@ -933,37 +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_PCO_DATA_CHANGED:
-                    handlePcoData((AsyncResult) msg.obj);
-                    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;
@@ -976,55 +962,94 @@
 
         @Override
         public String getName() {
-            return mIsNrAdvanced ? STATE_CONNECTED_NR_ADVANCED : STATE_CONNECTED;
-        }
-
-        private void updateNrAdvancedState() {
-            if (!isNrConnected()) {
-                log("NR state changed. Sending EVENT_NR_STATE_CHANGED");
-                sendMessage(EVENT_NR_STATE_CHANGED);
-                return;
-            }
-            if (!isNrAdvanced()) {
-                if (DBG) log("updateNrAdvancedState: CONNECTED_NR_ADVANCED -> CONNECTED");
-                transitionWithTimerTo(mNrConnectedState);
-            } else {
-                if (DBG) log("updateNrAdvancedState: CONNECTED -> CONNECTED_NR_ADVANCED");
-                transitionTo(mNrConnectedState);
-            }
-            mIsNrAdvanced = isNrAdvanced();
-        }
-
-        private void handlePcoData(AsyncResult ar) {
-            if (ar.exception != null) {
-                loge("PCO_DATA exception: " + ar.exception);
-                return;
-            }
-            PcoData pcodata = (PcoData) ar.result;
-            if (pcodata == null) {
-                return;
-            }
-            log("EVENT_PCO_DATA_CHANGED: pco data: " + pcodata);
-            DcTracker dcTracker = mPhone.getDcTracker(
-                    AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-            DataConnection dc =
-                    dcTracker != null ? dcTracker.getDataConnectionByContextId(pcodata.cid) : null;
-            ApnSetting apnSettings = dc != null ? dc.getApnSetting() : null;
-            if (apnSettings != null && apnSettings.canHandleType(ApnSetting.TYPE_DEFAULT)
-                    && mNrAdvancedCapablePcoId > 0
-                    && pcodata.pcoId == mNrAdvancedCapablePcoId
-            ) {
-                log("EVENT_PCO_DATA_CHANGED: NR_ADVANCED is allowed by PCO. length:"
-                        + pcodata.contents.length + ",value: " + Arrays.toString(pcodata.contents));
-                mIsNrAdvancedAllowedByPco = pcodata.contents.length > 0
-                        && pcodata.contents[pcodata.contents.length - 1] == 1;
-                updateNrAdvancedState();
-            }
+            return STATE_CONNECTED;
         }
     }
 
     private final NrConnectedState mNrConnectedState = new NrConnectedState();
 
+    /**
+     * 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 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);
@@ -1062,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();
@@ -1103,6 +1129,8 @@
             removeMessages(EVENT_PRIMARY_TIMER_EXPIRED);
             mIsPrimaryTimerActive = false;
             mPrimaryTimerState = "";
+            transitionToCurrentState();
+            return;
         }
 
         if (mIsSecondaryTimerActive && !mSecondaryTimerState.equals(currentState)) {
@@ -1114,18 +1142,25 @@
             removeMessages(EVENT_SECONDARY_TIMER_EXPIRED);
             mIsSecondaryTimerActive = false;
             mSecondaryTimerState = "";
+            transitionToCurrentState();
+            return;
         }
 
         if (mIsPrimaryTimerActive || mIsSecondaryTimerActive) {
             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();
+                }
             }
         }
     }
@@ -1222,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;
     }
 
     /**
@@ -1247,15 +1280,24 @@
 
         // 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;
         }
 
+        int bandwidths = 0;
+        if (mPhone.getServiceStateTracker().getPhysicalChannelConfigList() != null) {
+            bandwidths = mPhone.getServiceStateTracker().getPhysicalChannelConfigList()
+                    .stream()
+                    .filter(config -> mIncludeLteForNrAdvancedThresholdBandwidth
+                            || config.getNetworkType() == TelephonyManager.NETWORK_TYPE_NR)
+                    .map(PhysicalChannelConfig::getCellBandwidthDownlinkKhz)
+                    .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
-                && IntStream.of(mPhone.getServiceState().getCellBandwidths()).sum()
-                < mNrAdvancedThresholdBandwidth) {
+        if (mNrAdvancedThresholdBandwidth > 0 && bandwidths < mNrAdvancedThresholdBandwidth) {
             return false;
         }
 
@@ -1265,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;
@@ -1295,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];
@@ -1354,16 +1384,16 @@
         pw.println("mIsPhysicalChannelConfigOn=" + mIsPhysicalChannelConfigOn);
         pw.println("mIsPrimaryTimerActive=" + mIsPrimaryTimerActive);
         pw.println("mIsSecondaryTimerActive=" + mIsSecondaryTimerActive);
-        pw.println("mIsTimerRestEnabledForLegacyStateRRCIdle="
-                + mIsTimerResetEnabledForLegacyStateRRCIdle);
+        pw.println("mIsTimerResetEnabledForLegacyStateRrcIdle="
+                + mIsTimerResetEnabledForLegacyStateRrcIdle);
         pw.println("mLtePlusThresholdBandwidth=" + mLtePlusThresholdBandwidth);
         pw.println("mNrAdvancedThresholdBandwidth=" + mNrAdvancedThresholdBandwidth);
+        pw.println("mAdditionalNrAdvancedBandsList="
+                + Arrays.toString(mAdditionalNrAdvancedBandsList));
         pw.println("mPrimaryTimerState=" + mPrimaryTimerState);
         pw.println("mSecondaryTimerState=" + mSecondaryTimerState);
         pw.println("mPreviousState=" + mPreviousState);
-        pw.println("mPhysicalLinkStatus=" + mPhysicalLinkStatus);
-        pw.println("mAdditionalNrAdvancedBandsList="
-                + Arrays.toString(mAdditionalNrAdvancedBandsList));
+        pw.println("mPhysicalLinkStatus=" + DataUtils.linkStatusToString(mPhysicalLinkStatus));
         pw.println("mIsPhysicalChannelConfig16Supported=" + mIsPhysicalChannelConfig16Supported);
         pw.println("mIsNrAdvancedAllowedByPco=" + mIsNrAdvancedAllowedByPco);
         pw.println("mNrAdvancedCapablePcoId=" + mNrAdvancedCapablePcoId);
diff --git a/src/java/com/android/internal/telephony/NitzData.java b/src/java/com/android/internal/telephony/NitzData.java
index 6b19e08..8430585 100644
--- a/src/java/com/android/internal/telephony/NitzData.java
+++ b/src/java/com/android/internal/telephony/NitzData.java
@@ -21,6 +21,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.telephony.Rlog;
 
+import java.time.DateTimeException;
 import java.time.LocalDateTime;
 import java.time.ZoneOffset;
 import java.util.Objects;
@@ -39,9 +40,6 @@
     private static final int MS_PER_QUARTER_HOUR = 15 * 60 * 1000;
     private static final int MS_PER_HOUR = 60 * 60 * 1000;
 
-    /* Time stamp after 19 January 2038 is not supported under 32 bit */
-    private static final int MAX_NITZ_YEAR = 2037;
-
     private static final Pattern NITZ_SPLIT_PATTERN = Pattern.compile("[/:,+-]");
 
     // Stored For logging / debugging only.
@@ -77,22 +75,37 @@
         try {
             String[] nitzSubs = NITZ_SPLIT_PATTERN.split(nitz);
 
-            int year = 2000 + Integer.parseInt(nitzSubs[0]);
-            if (year > MAX_NITZ_YEAR) {
-                if (ServiceStateTracker.DBG) {
-                    Rlog.e(LOG_TAG, "NITZ year: " + year + " exceeds limit, skip NITZ time update");
-                }
-                return null;
+            int year = Integer.parseInt(nitzSubs[0]);
+            if (year < 1 || year > 99) {
+                // 0 > year > 99 imply an invalid string.
+                //
+                // At the time of this comment (year 2023), a zero year is considered invalid and
+                // assumed to be the result of invalid data being converted to zero in the code that
+                // turns the binary NITZ into a string. For the next few decades at least, Android
+                // devices should not need to interpret zero. Hopefully, NITZ will be replaced by
+                // the time that's not true, or folks dealing the Y2K1 issue can handle it.
+                //
+                // DateTimeException is also thrown by LocalDateTime below if the values are out of
+                // range and will be handled in the catch block.
+                throw new DateTimeException("Invalid NITZ year == 0");
             }
 
+            // Values < {current year} could be considered invalid but are used in test code, so no
+            // window is applied to adjust low values < {current year} with "+ 2100" (and would also
+            // need to consider zero as valid). Code that processes the NitzData is in a better
+            // position to log and discard obviously invalid NITZ signals from past years.
+            year += 2000;
+
             int month = Integer.parseInt(nitzSubs[1]);
             int date = Integer.parseInt(nitzSubs[2]);
             int hour = Integer.parseInt(nitzSubs[3]);
             int minute = Integer.parseInt(nitzSubs[4]);
             int second = Integer.parseInt(nitzSubs[5]);
 
-            /* NITZ time (hour:min:sec) will be in UTC but it supplies the timezone
-             * offset as well (which we won't worry about until later) */
+            // NITZ time (hour:min:sec) will be in UTC but it supplies the timezone
+            // offset as well (which we won't worry about until later).
+            // The LocalDateTime.of() will throw DateTimeException for values outside the allowed
+            // range for the Gregorian calendar.
             long epochMillis = LocalDateTime.of(year, month, date, hour, minute, second)
                     .toInstant(ZoneOffset.UTC)
                     .toEpochMilli();
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 4afccd1..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.sysprop.TelephonyProperties;
 import android.telecom.VideoProfile;
 import android.telephony.AccessNetworkConstants;
-import android.telephony.Annotation.ApnType;
+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,14 +67,15 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager;
-import android.telephony.data.ApnSetting;
+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;
-import android.util.SparseArray;
 import android.util.Xml;
 
 import com.android.ims.ImsCall;
@@ -80,15 +88,17 @@
 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.dataconnection.DataConnectionReasons;
-import com.android.internal.telephony.dataconnection.DataEnabledSettings;
-import com.android.internal.telephony.dataconnection.DcTracker;
-import com.android.internal.telephony.dataconnection.TransportManager;
+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;
 import com.android.internal.telephony.metrics.VoiceCallSessionStats;
+import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.test.SimulatedRadioControl;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
 import com.android.internal.telephony.uicc.IccFileHandler;
@@ -172,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;
@@ -222,11 +233,8 @@
     // Radio state change
     protected static final int EVENT_RADIO_STATE_CHANGED            = 47;
     protected static final int EVENT_SET_CARRIER_DATA_ENABLED       = 48;
-    protected static final int EVENT_DEVICE_PROVISIONED_CHANGE      = 49;
-    protected static final int EVENT_DEVICE_PROVISIONING_DATA_SETTING_CHANGE = 50;
     protected static final int EVENT_GET_AVAILABLE_NETWORKS_DONE    = 51;
 
-    private static final int EVENT_ALL_DATA_DISCONNECTED                  = 52;
     protected static final int EVENT_UICC_APPS_ENABLEMENT_STATUS_CHANGED  = 53;
     protected static final int EVENT_UICC_APPS_ENABLEMENT_SETTING_CHANGED = 54;
     protected static final int EVENT_GET_UICC_APPS_ENABLEMENT_DONE        = 55;
@@ -239,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_";
@@ -267,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.
      */
@@ -303,11 +319,6 @@
     public CommandsInterface mCi;
     protected int mVmCount = 0;
     private boolean mDnsCheckDisabled;
-    // Data connection trackers. For each transport type (e.g. WWAN, WLAN), there will be a
-    // corresponding DcTracker. The WWAN DcTracker is for cellular data connections while
-    // WLAN DcTracker is for IWLAN data connection. For IWLAN legacy mode, only one (WWAN) DcTracker
-    // will be created.
-    protected final SparseArray<DcTracker> mDcTrackers = new SparseArray<>();
     protected DataNetworkController mDataNetworkController;
     /* Used for dispatching signals to configured carrier apps */
     protected CarrierSignalAgent mCarrierSignalAgent;
@@ -340,15 +351,13 @@
     @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;
     protected DeviceStateMonitor mDeviceStateMonitor;
     protected DisplayInfoController mDisplayInfoController;
-    protected TransportManager mTransportManager;
     protected AccessNetworksManager mAccessNetworksManager;
-    protected DataEnabledSettings mDataEnabledSettings;
     // Used for identify the carrier of current subscription
     protected CarrierResolver mCarrierResolver;
     protected SignalStrengthController mSignalStrengthController;
@@ -399,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();
 
@@ -428,8 +437,6 @@
 
     protected final RegistrantList mEmergencyCallToggledRegistrants = new RegistrantList();
 
-    private final RegistrantList mAllDataDisconnectedRegistrants = new RegistrantList();
-
     private final RegistrantList mCellInfoRegistrants = new RegistrantList();
 
     private final RegistrantList mRedialRegistrants = new RegistrantList();
@@ -449,6 +456,8 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     protected final Context mContext;
 
+    protected SubscriptionManagerService mSubscriptionManagerService;
+
     /**
      * PhoneNotifier is an abstraction for all system-wide
      * state change notification. DefaultPhoneNotifier is
@@ -474,9 +483,9 @@
 
     protected LinkBandwidthEstimator mLinkBandwidthEstimator;
 
-    /** The flag indicating using the new data stack or not. */
-    // This flag and the old data stack code will be deleted in Android 14.
-    private final boolean mNewDataStackEnabled;
+    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();
@@ -580,10 +589,6 @@
                 .makeAppSmsManager(context);
         mLocalLog = new LocalLog(64);
 
-        if (TelephonyUtils.IS_DEBUGGABLE) {
-            mTelephonyTester = new TelephonyTester(this);
-        }
-
         setUnitTestMode(unitTestMode);
 
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
@@ -619,13 +624,17 @@
         // Initialize SMS stats
         mSmsStats = new SmsStats(this);
 
-        mNewDataStackEnabled = !mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_force_disable_telephony_new_data_stack);
+        mSubscriptionManagerService = SubscriptionManagerService.getInstance();
+        m2gAdminUpdater = new TelephonyAdminReceiver(context, this);
 
         if (getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
             return;
         }
 
+        if (TelephonyUtils.IS_DEBUGGABLE) {
+            mTelephonyTester = new TelephonyTester(this);
+        }
+
         // Initialize device storage and outgoing SMS usage monitors for SMSDispatchers.
         mTelephonyComponentFactory = telephonyComponentFactory;
         mSmsStorageMonitor = mTelephonyComponentFactory.inject(SmsStorageMonitor.class.getName())
@@ -814,11 +823,6 @@
                 break;
             }
 
-            case EVENT_ALL_DATA_DISCONNECTED:
-                if (areAllDataDisconnected()) {
-                    mAllDataDisconnectedRegistrants.notifyRegistrants();
-                }
-                break;
             case EVENT_GET_USAGE_SETTING_DONE:
                 ar = (AsyncResult) msg.obj;
                 if (ar.exception == null) {
@@ -871,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) {
@@ -894,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;
@@ -906,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:
@@ -985,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) {
@@ -1442,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;
@@ -1476,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 {
@@ -1932,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);
     }
 
     /**
@@ -1951,13 +1947,6 @@
     }
 
     /**
-     * @return The instance of transport manager.
-     */
-    public TransportManager getTransportManager() {
-        return null;
-    }
-
-    /**
      * @return The instance of access networks manager.
      */
     public AccessNetworksManager getAccessNetworksManager() {
@@ -2326,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;
@@ -2401,17 +2396,18 @@
      * Loads the allowed network type from subscription database.
      */
     public void loadAllowedNetworksFromSubscriptionDatabase() {
-        // Try to load ALLOWED_NETWORK_TYPES from SIMINFO.
-        if (SubscriptionController.getInstance() == null) {
-            return;
+        String result = null;
+        SubscriptionInfoInternal subInfo = mSubscriptionManagerService
+                .getSubscriptionInfoInternal(getSubId());
+        if (subInfo != null) {
+            result = subInfo.getAllowedNetworkTypesForReasons();
         }
 
-        String result = SubscriptionController.getInstance().getSubscriptionProperty(
-                getSubId(),
-                SubscriptionManager.ALLOWED_NETWORK_TYPES);
         // After fw load network type from DB, do unlock if subId is valid.
-        mIsAllowedNetworkTypesLoadedFromDb = SubscriptionManager.isValidSubscriptionId(getSubId());
-        if (result == null) {
+        mIsAllowedNetworkTypesLoadedFromDb = SubscriptionManager.isValidSubscriptionId(
+                getSubId());
+
+        if (TextUtils.isEmpty(result)) {
             return;
         }
 
@@ -2421,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);
@@ -2466,7 +2462,15 @@
         }
     }
 
-    private String convertAllowedNetworkTypeMapIndexToDbName(int reason) {
+    /**
+     * Convert the allowed network types reason to string.
+     *
+     * @param reason The allowed network types reason.
+     *
+     * @return The converted string.
+     */
+    public static String convertAllowedNetworkTypeMapIndexToDbName(
+            @TelephonyManager.AllowedNetworkTypesReason int reason) {
         switch (reason) {
             case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER:
                 return ALLOWED_NETWORK_TYPES_TEXT_USER;
@@ -2477,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?");
         }
     }
 
@@ -2977,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);
     }
 
@@ -2986,6 +2996,9 @@
      * emergency operator.
      */
     public boolean isInEcm() {
+        if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
+            return EmergencyStateTracker.getInstance().isInEcm();
+        }
         return mIsPhoneInEcmState;
     }
 
@@ -2994,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());
     }
@@ -3730,18 +3746,11 @@
      * @return true if there is a matching DUN APN.
      */
     public boolean hasMatchedTetherApnSetting() {
-        if (isUsingNewDataStack()) {
-            NetworkRegistrationInfo nrs = getServiceState().getNetworkRegistrationInfo(
-                    NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-            if (nrs != null) {
-                return getDataNetworkController().getDataProfileManager()
-                        .isTetheringDataProfileExisting(nrs.getAccessNetworkTechnology());
-            }
-            return false;
-        }
-        if (getDcTracker(AccessNetworkConstants.TRANSPORT_TYPE_WWAN) != null) {
-            return getDcTracker(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                    .hasMatchedTetherApnSetting();
+        NetworkRegistrationInfo nrs = getServiceState().getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        if (nrs != null) {
+            return getDataNetworkController().getDataProfileManager()
+                    .isTetheringDataProfileExisting(nrs.getAccessNetworkTechnology());
         }
         return false;
     }
@@ -3752,31 +3761,10 @@
      * @return {@code true} if internet data is allowed to be established.
      */
     public boolean isDataAllowed() {
-        if (isUsingNewDataStack()) {
-            return getDataNetworkController().isInternetDataAllowed();
-        }
-        return isDataAllowed(ApnSetting.TYPE_DEFAULT, null);
+        return getDataNetworkController().isInternetDataAllowed();
     }
 
     /**
-     * Report on whether data connectivity is allowed.
-     *
-     * @param apnType APN type
-     * @param reasons The reasons that data can/can't be established. This is an output param.
-     * @return True if data is allowed to be established
-     */
-    public boolean isDataAllowed(@ApnType int apnType, DataConnectionReasons reasons) {
-        if (mAccessNetworksManager != null) {
-            int transport = mAccessNetworksManager.getCurrentTransport(apnType);
-            if (getDcTracker(transport) != null) {
-                return getDcTracker(transport).isDataAllowed(reasons);
-            }
-        }
-        return false;
-    }
-
-
-    /**
      * Action set from carrier signalling broadcast receivers to enable/disable metered apns.
      */
     public void carrierActionSetMeteredApnsEnabled(boolean enabled) {
@@ -3932,16 +3920,6 @@
         return null;
     }
 
-    /**
-     * Get the current for the default apn DataState. No change notification
-     * exists at this interface -- use
-     * {@link android.telephony.PhoneStateListener} instead.
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public PhoneConstants.DataState getDataConnectionState() {
-        return getDataConnectionState(ApnSetting.TYPE_DEFAULT_STRING);
-    }
-
     public void notifyCallForwardingIndicator() {
     }
 
@@ -4117,14 +4095,7 @@
      */
     @UnsupportedAppUsage
     public int getSubId() {
-        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().getSubIdUsingPhoneId(mPhoneId);
+        return mSubscriptionManagerService.getSubId(mPhoneId);
     }
 
     /**
@@ -4420,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));
         }
     }
@@ -4438,7 +4410,12 @@
     }
 
     private int getResolvedUsageSetting(int subId) {
-        SubscriptionInfo subInfo = SubscriptionController.getInstance().getSubscriptionInfo(subId);
+        SubscriptionInfo subInfo = null;
+        SubscriptionInfoInternal subInfoInternal = mSubscriptionManagerService
+                .getSubscriptionInfoInternal(subId);
+        if (subInfoInternal != null) {
+            subInfo = subInfoInternal.toSubscriptionInfo();
+        }
 
         if (subInfo == null
                 || subInfo.getUsageSetting() == SubscriptionManager.USAGE_SETTING_UNKNOWN) {
@@ -4621,42 +4598,6 @@
         return false;
     }
 
-    /**
-     * @return True if all data connections are disconnected.
-     */
-    public boolean areAllDataDisconnected() {
-        if (mAccessNetworksManager != null) {
-            for (int transport : mAccessNetworksManager.getAvailableTransports()) {
-                if (getDcTracker(transport) != null
-                        && !getDcTracker(transport).areAllDataDisconnected()) {
-                    return false;
-                }
-            }
-        }
-        return true;
-    }
-
-    public void registerForAllDataDisconnected(Handler h, int what) {
-        mAllDataDisconnectedRegistrants.addUnique(h, what, null);
-        if (mAccessNetworksManager != null) {
-            for (int transport : mAccessNetworksManager.getAvailableTransports()) {
-                if (getDcTracker(transport) != null
-                        && !getDcTracker(transport).areAllDataDisconnected()) {
-                    getDcTracker(transport).registerForAllDataDisconnected(
-                            this, EVENT_ALL_DATA_DISCONNECTED);
-                }
-            }
-        }
-    }
-
-    public void unregisterForAllDataDisconnected(Handler h) {
-        mAllDataDisconnectedRegistrants.remove(h);
-    }
-
-    public DataEnabledSettings getDataEnabledSettings() {
-        return mDataEnabledSettings;
-    }
-
     @UnsupportedAppUsage
     public IccSmsInterfaceManager getIccSmsInterfaceManager(){
         return null;
@@ -4787,16 +4728,6 @@
         return isEmergencyCallOnly;
     }
 
-    /**
-     * Get data connection tracker based on the transport type
-     *
-     * @param transportType Transport type defined in AccessNetworkConstants.TransportType
-     * @return The data connection tracker. Null if not found.
-     */
-    public @Nullable DcTracker getDcTracker(int transportType) {
-        return mDcTrackers.get(transportType);
-    }
-
     // Return true if either CSIM or RUIM app is present. By default it returns false.
     public boolean isCdmaSubscriptionAppPresent() {
         return false;
@@ -4820,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;
     }
@@ -4967,11 +4912,600 @@
     }
 
     /**
-     * @return {@code true} if using the new telephony data stack.
+     * Returns the user's last setting for terminal-based call waiting
+     * @param forCsOnly indicates the caller expects the result for CS calls only
      */
-    // This flag and the old data stack code will be deleted in Android 14.
-    public boolean isUsingNewDataStack() {
-        return mNewDataStackEnabled;
+    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) {
@@ -4996,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());
@@ -5009,7 +5543,6 @@
         pw.println(" needsOtaServiceProvisioning=" + needsOtaServiceProvisioning());
         pw.println(" isInEmergencySmsMode=" + isInEmergencySmsMode());
         pw.println(" isEcmCanceledForEmergency=" + isEcmCanceledForEmergency());
-        pw.println(" isUsingNewDataStack=" + isUsingNewDataStack());
         pw.println(" service state=" + getServiceState());
         pw.flush();
         pw.println("++++++++++++++++++++++++++++++++");
@@ -5025,16 +5558,6 @@
             pw.println("++++++++++++++++++++++++++++++++");
         }
 
-        if (mAccessNetworksManager != null) {
-            for (int transport : mAccessNetworksManager.getAvailableTransports()) {
-                if (getDcTracker(transport) != null) {
-                    getDcTracker(transport).dump(fd, pw, args);
-                    pw.flush();
-                    pw.println("++++++++++++++++++++++++++++++++");
-                }
-            }
-        }
-
         if (mDataNetworkController != null) {
             try {
                 mDataNetworkController.dump(fd, pw, args);
diff --git a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
index 05c325e..8b95824 100644
--- a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
+++ b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
@@ -35,6 +35,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.telephony.Rlog;
 
 import java.util.HashMap;
@@ -71,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
@@ -122,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.
      */
@@ -180,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);
@@ -366,7 +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++) {
-                SubscriptionController.getInstance().clearSubInfoRecord(phoneId);
+                SubscriptionManagerService.getInstance().markSubscriptionsInactive(phoneId);
                 subInfoCleared = true;
                 mPhones[phoneId].mCi.onSlotActiveStatusChange(
                         SubscriptionManager.isValidPhoneId(phoneId));
@@ -400,7 +419,7 @@
                         + "setting VOICE & SMS subId to -1 (No Preference)");
 
                 //Set the default VOICE subId to -1 ("No Preference")
-                SubscriptionController.getInstance().setDefaultVoiceSubId(
+                SubscriptionManagerService.getInstance().setDefaultVoiceSubId(
                         SubscriptionManager.INVALID_SUBSCRIPTION_ID);
 
                 //TODO:: Set the default SMS sub to "No Preference". Tracking this bug (b/227386042)
@@ -465,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;
     }
 
      /**
@@ -500,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 3361b74..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,7 +31,6 @@
 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.Settings;
@@ -50,6 +51,7 @@
 import com.android.internal.telephony.imsphone.ImsPhoneFactory;
 import com.android.internal.telephony.metrics.MetricsCollector;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.telephony.util.NotificationChannelController;
 import com.android.internal.util.IndentingPrintWriter;
@@ -83,8 +85,7 @@
     private static IntentBroadcaster sIntentBroadcaster;
     private static @Nullable EuiccController sEuiccController;
     private static @Nullable EuiccCardController sEuiccCardController;
-
-    static private SubscriptionInfoUpdater sSubInfoRecordUpdater = null;
+    private static SubscriptionManagerService sSubscriptionManagerService;
 
     @UnsupportedAppUsage
     static private boolean sMadeDefaults = false;
@@ -117,6 +118,7 @@
         synchronized (sLockProxyPhones) {
             if (!sMadeDefaults) {
                 sContext = context;
+
                 // create the telephony device controller.
                 TelephonyDevController.create();
 
@@ -179,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 {
@@ -194,12 +196,12 @@
                 // call getInstance()
                 sUiccController = UiccController.make(context);
 
-                Rlog.i(LOG_TAG, "Creating SubscriptionController");
-                TelephonyComponentFactory.getInstance().inject(SubscriptionController.class.
-                        getName()).initSubscriptionController(context);
+                Rlog.i(LOG_TAG, "Creating SubscriptionManagerService");
+                sSubscriptionManagerService = new SubscriptionManagerService(context,
+                        Looper.myLooper());
+
                 TelephonyComponentFactory.getInstance().inject(MultiSimSettingController.class.
-                        getName()).initMultiSimSettingController(context,
-                        SubscriptionController.getInstance());
+                        getName()).initMultiSimSettingController(context);
 
                 if (context.getPackageManager().hasSystemFeature(
                         PackageManager.FEATURE_TELEPHONY_EUICC)) {
@@ -231,14 +233,6 @@
 
                 sMadeDefaults = true;
 
-                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)) {
@@ -376,10 +370,6 @@
         }
     }
 
-    public static SubscriptionInfoUpdater getSubscriptionInfoUpdater() {
-        return sSubInfoRecordUpdater;
-    }
-
     /**
      * Get the network factory associated with a given phone ID.
      * @param phoneId the phone id
@@ -416,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) {
@@ -433,7 +422,7 @@
     /* Gets the default subscription */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public static int getDefaultSubscription() {
-        return SubscriptionController.getInstance().getDefaultSubId();
+        return SubscriptionManagerService.getInstance().getDefaultSubId();
     }
 
     /* Returns User SMS Prompt property,  enabled or not */
@@ -461,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) {
@@ -576,21 +553,14 @@
         pw.decreaseIndent();
         pw.println("++++++++++++++++++++++++++++++++");
 
-        pw.println("SubscriptionController:");
-        pw.increaseIndent();
-        try {
-            SubscriptionController.getInstance().dump(fd, pw, args);
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
         pw.flush();
         pw.decreaseIndent();
         pw.println("++++++++++++++++++++++++++++++++");
 
-        pw.println("SubInfoRecordUpdater:");
+        pw.println("sRadioHalCapabilities:");
         pw.increaseIndent();
         try {
-            sSubInfoRecordUpdater.dump(fd, pw, args);
+            sRadioHalCapabilities.dump(fd, pw, args);
         } catch (Exception e) {
             e.printStackTrace();
         }
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 b1dfa5f..d30a73c 100644
--- a/src/java/com/android/internal/telephony/PhoneSubInfoController.java
+++ b/src/java/com/android/internal/telephony/PhoneSubInfoController.java
@@ -27,22 +27,29 @@
 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;
 import android.os.TelephonyServiceManager.ServiceRegisterer;
-import android.os.UserHandle;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyFrameworkInitializer;
+import android.text.TextUtils;
 import android.util.EventLog;
 
+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;
@@ -147,14 +154,13 @@
     public String getSubscriberIdForSubscriber(int subId, String callingPackage,
             String callingFeatureId) {
         String message = "getSubscriberIdForSubscriber";
-
-        enforceCallingPackage(callingPackage, Binder.getCallingUid(), message);
+        mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
 
         long identity = Binder.clearCallingIdentity();
         boolean isActive;
         try {
-            isActive = SubscriptionController.getInstance().isActiveSubId(subId, callingPackage,
-                    callingFeatureId);
+            isActive = SubscriptionManagerService.getInstance().isActiveSubId(subId,
+                    callingPackage, callingFeatureId);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -168,7 +174,12 @@
             }
             identity = Binder.clearCallingIdentity();
             try {
-                return SubscriptionController.getInstance().getImsiPrivileged(subId);
+                SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
+                        .getSubscriptionInfoInternal(subId);
+                if (subInfo != null && !TextUtils.isEmpty(subInfo.getImsi())) {
+                    return subInfo.getImsi();
+                }
+                return null;
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
@@ -316,28 +327,6 @@
                 "Requires MODIFY_PHONE_STATE");
     }
 
-    /**
-     * Make sure the caller is the calling package itself
-     *
-     * @throws SecurityException if the caller is not the calling package
-     */
-    private void enforceCallingPackage(String callingPackage, int callingUid, String message) {
-        int packageUid = -1;
-        PackageManager pm = mContext.createContextAsUser(
-                UserHandle.getUserHandleForUid(callingUid), 0).getPackageManager();
-        if (pm != null) {
-            try {
-                packageUid = pm.getPackageUid(callingPackage, 0);
-            } catch (PackageManager.NameNotFoundException e) {
-                // packageUid is -1
-            }
-        }
-        if (packageUid != callingUid) {
-            throw new SecurityException(message + ": Package " + callingPackage
-                    + " does not belong to " + callingUid);
-        }
-    }
-
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private int getDefaultSubscription() {
         return  PhoneFactory.getDefaultSubscription();
@@ -359,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) {
@@ -389,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 {
@@ -418,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 {
@@ -438,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;
             }
@@ -491,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 {
@@ -581,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 76f0041..ed9982e 100644
--- a/src/java/com/android/internal/telephony/ProxyController.java
+++ b/src/java/com/android/internal/telephony/ProxyController.java
@@ -28,7 +28,6 @@
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 import android.telephony.RadioAccessFamily;
-import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
@@ -48,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;
 
@@ -157,34 +158,6 @@
         logd("Constructor - Exit");
     }
 
-    public void registerForAllDataDisconnected(int subId, Handler h, int what) {
-        int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
-
-        if (SubscriptionManager.isValidPhoneId(phoneId)) {
-            mPhones[phoneId].registerForAllDataDisconnected(h, what);
-        }
-    }
-
-    public void unregisterForAllDataDisconnected(int subId, Handler h) {
-        int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
-
-        if (SubscriptionManager.isValidPhoneId(phoneId)) {
-            mPhones[phoneId].unregisterForAllDataDisconnected(h);
-        }
-    }
-
-
-    public boolean areAllDataDisconnected(int subId) {
-        int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
-
-        if (SubscriptionManager.isValidPhoneId(phoneId)) {
-            return mPhones[phoneId].areAllDataDisconnected();
-        } else {
-            // if we can't find a phone for the given subId, it is disconnected.
-            return true;
-        }
-    }
-
     /**
      * Get phone radio type and access technology.
      *
@@ -241,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);
@@ -386,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;
             }
@@ -510,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--;
@@ -604,7 +591,6 @@
             clearTransaction();
         } else {
             intent = new Intent(TelephonyIntents.ACTION_SET_RADIO_CAPABILITY_FAILED);
-
             // now revert.
             mTransactionFailed = false;
             RadioAccessFamily[] rafs = new RadioAccessFamily[mPhones.length];
@@ -631,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 49e8297..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;
@@ -41,10 +50,13 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.Trace;
 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;
@@ -54,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;
@@ -66,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;
@@ -91,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;
@@ -132,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 */
@@ -158,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
 
@@ -197,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
@@ -232,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;
@@ -361,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);
                     }
@@ -412,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));
         }
     }
 
@@ -427,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));
             }
         }
 
@@ -443,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();
@@ -466,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);
@@ -495,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;
@@ -536,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);
                 }
             }
@@ -585,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");
@@ -602,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) {
                 }
 
@@ -617,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) {
                     }
                 }
@@ -626,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) {
                     }
                 }
@@ -635,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) {
                     }
                 }
@@ -644,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) {
                     }
                 }
@@ -653,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) {
                     }
                 }
@@ -662,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) {
                     }
                 }
@@ -671,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");
                 }
@@ -700,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;
@@ -730,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) {
@@ -752,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) {
@@ -923,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");
                 }
@@ -1002,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);
@@ -1043,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
@@ -1059,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);
@@ -1072,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;
         }
@@ -1113,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) {
@@ -1121,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;
@@ -1162,6 +1291,8 @@
 
     private void addRequest(RILRequest rr) {
         acquireWakeLock(rr, FOR_WAKELOCK);
+        Trace.asyncTraceForTrackBegin(
+                Trace.TRACE_TAG_NETWORK, "RIL", RILUtils.requestToString(rr.mRequest), rr.mSerial);
         synchronized (mRequestList) {
             rr.mStartTimeMs = SystemClock.elapsedRealtime();
             mRequestList.append(rr.mSerial, rr);
@@ -1202,7 +1333,7 @@
             try {
                 simProxy.getIccCardStatus(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "getIccCardStatus", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "getIccCardStatus", e);
             }
         }
     }
@@ -1249,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);
             }
         }
     }
@@ -1276,7 +1407,7 @@
                         RILUtils.convertNullToEmptyString(newPin),
                         RILUtils.convertNullToEmptyString(aid));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "supplyIccPukForApp", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "supplyIccPukForApp", e);
             }
         }
     }
@@ -1302,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);
             }
         }
     }
@@ -1329,7 +1460,7 @@
                         RILUtils.convertNullToEmptyString(newPin2),
                         RILUtils.convertNullToEmptyString(aid));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "supplyIccPuk2ForApp", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "supplyIccPuk2ForApp", e);
             }
         }
     }
@@ -1357,7 +1488,7 @@
                         RILUtils.convertNullToEmptyString(newPin),
                         RILUtils.convertNullToEmptyString(aid));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "changeIccPinForApp", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "changeIccPinForApp", e);
             }
         }
     }
@@ -1385,7 +1516,7 @@
                         RILUtils.convertNullToEmptyString(newPin2),
                         RILUtils.convertNullToEmptyString(aid));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "changeIccPin2ForApp", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "changeIccPin2ForApp", e);
             }
         }
     }
@@ -1407,7 +1538,7 @@
                         RILUtils.convertNullToEmptyString(netpin));
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(
-                        NETWORK_SERVICE, "supplyNetworkDepersonalization", e);
+                        HAL_SERVICE_NETWORK, "supplyNetworkDepersonalization", e);
             }
         }
     }
@@ -1417,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);
 
@@ -1430,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) {
@@ -1462,7 +1593,7 @@
             try {
                 voiceProxy.getCurrentCalls(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "getCurrentCalls", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getCurrentCalls", e);
             }
         }
     }
@@ -1478,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) {
@@ -1489,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.");
@@ -1506,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);
 
@@ -1518,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.");
@@ -1534,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);
 
@@ -1546,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.");
@@ -1562,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);
 
@@ -1573,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.");
@@ -1588,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);
@@ -1606,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);
             }
         }
     }
@@ -1615,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);
 
@@ -1628,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");
@@ -1647,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);
             }
         }
     }
@@ -1672,7 +1807,7 @@
             try {
                 voiceProxy.hangup(rr.mSerial, gsmIndex);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "hangup", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "hangup", e);
             }
         }
     }
@@ -1692,7 +1827,7 @@
             try {
                 voiceProxy.hangupWaitingOrBackground(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "hangupWaitingOrBackground", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "hangupWaitingOrBackground", e);
             }
         }
     }
@@ -1713,7 +1848,7 @@
                 voiceProxy.hangupForegroundResumeBackground(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(
-                        VOICE_SERVICE, "hangupForegroundResumeBackground", e);
+                        HAL_SERVICE_VOICE, "hangupForegroundResumeBackground", e);
             }
         }
     }
@@ -1732,7 +1867,8 @@
             try {
                 voiceProxy.switchWaitingOrHoldingAndActive(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "switchWaitingOrHoldingAndActive", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE,
+                        "switchWaitingOrHoldingAndActive", e);
             }
         }
     }
@@ -1750,7 +1886,7 @@
             try {
                 voiceProxy.conference(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "conference", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "conference", e);
             }
         }
     }
@@ -1768,7 +1904,7 @@
             try {
                 voiceProxy.rejectCall(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "rejectCall", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "rejectCall", e);
             }
         }
     }
@@ -1787,7 +1923,7 @@
             try {
                 voiceProxy.getLastCallFailCause(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "getLastCallFailCause", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getLastCallFailCause", e);
             }
         }
     }
@@ -1806,7 +1942,7 @@
             try {
                 networkProxy.getSignalStrength(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getSignalStrength", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getSignalStrength", e);
             }
         }
     }
@@ -1830,7 +1966,7 @@
             try {
                 networkProxy.getVoiceRegistrationState(rr.mSerial, overrideHalVersion);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getVoiceRegistrationState", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getVoiceRegistrationState", e);
             }
         }
     }
@@ -1854,7 +1990,7 @@
             try {
                 networkProxy.getDataRegistrationState(rr.mSerial, overrideHalVersion);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getDataRegistrationState", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getDataRegistrationState", e);
             }
         }
     }
@@ -1872,7 +2008,7 @@
             try {
                 networkProxy.getOperator(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getOperator", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getOperator", e);
             }
         }
     }
@@ -1895,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);
             }
         }
     }
@@ -1914,7 +2050,7 @@
             try {
                 voiceProxy.sendDtmf(rr.mSerial, c + "");
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "sendDtmf", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "sendDtmf", e);
             }
         }
     }
@@ -1936,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);
             }
         }
     }
@@ -1977,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);
             }
         }
     }
@@ -2007,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();
             }
         }
     }
@@ -2045,7 +2194,7 @@
                         RILUtils.convertNullToEmptyString(pin2),
                         RILUtils.convertNullToEmptyString(aid));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "iccIoForApp", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "iccIoForApp", e);
             }
         }
     }
@@ -2067,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);
             }
         }
     }
@@ -2086,7 +2235,7 @@
             try {
                 voiceProxy.cancelPendingUssd(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "cancelPendingUssd", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "cancelPendingUssd", e);
             }
         }
     }
@@ -2104,7 +2253,7 @@
             try {
                 voiceProxy.getClir(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "getClir", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getClir", e);
             }
         }
     }
@@ -2123,7 +2272,7 @@
             try {
                 voiceProxy.setClir(rr.mSerial, clirMode);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "setClir", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "setClir", e);
             }
         }
     }
@@ -2144,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);
             }
         }
     }
@@ -2167,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);
             }
         }
     }
@@ -2187,7 +2336,7 @@
             try {
                 voiceProxy.getCallWaiting(rr.mSerial, serviceClass);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "getCallWaiting", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getCallWaiting", e);
             }
         }
     }
@@ -2207,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);
             }
         }
     }
@@ -2228,7 +2377,7 @@
             try {
                 messagingProxy.acknowledgeLastIncomingGsmSms(rr.mSerial, success, cause);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE,
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING,
                         "acknowledgeLastIncomingGsmSms", e);
             }
         }
@@ -2248,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);
             }
         }
     }
@@ -2270,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);
             }
         }
     }
@@ -2301,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);
             }
         }
     }
@@ -2332,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);
             }
         }
     }
@@ -2357,7 +2506,7 @@
                         RILUtils.convertNullToEmptyString(oldPwd),
                         RILUtils.convertNullToEmptyString(newPwd));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "changeBarringPassword", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "changeBarringPassword", e);
             }
         }
     }
@@ -2376,7 +2525,7 @@
             try {
                 networkProxy.getNetworkSelectionMode(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getNetworkSelectionMode", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getNetworkSelectionMode", e);
             }
         }
     }
@@ -2396,7 +2545,7 @@
                 networkProxy.setNetworkSelectionModeAutomatic(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(
-                        NETWORK_SERVICE, "setNetworkSelectionModeAutomatic", e);
+                        HAL_SERVICE_NETWORK, "setNetworkSelectionModeAutomatic", e);
             }
         }
     }
@@ -2417,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);
             }
         }
     }
@@ -2436,7 +2586,7 @@
             try {
                 networkProxy.getAvailableNetworks(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getAvailableNetworks", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getAvailableNetworks", e);
             }
         }
     }
@@ -2451,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);
@@ -2468,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");
@@ -2484,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);
 
@@ -2495,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");
@@ -2521,7 +2671,7 @@
             try {
                 voiceProxy.startDtmf(rr.mSerial, c + "");
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "startDtmf", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "startDtmf", e);
             }
         }
     }
@@ -2539,7 +2689,7 @@
             try {
                 voiceProxy.stopDtmf(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "stopDtmf", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "stopDtmf", e);
             }
         }
     }
@@ -2559,7 +2709,7 @@
             try {
                 voiceProxy.separateConnection(rr.mSerial, gsmIndex);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "separateConnection", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "separateConnection", e);
             }
         }
     }
@@ -2578,7 +2728,7 @@
             try {
                 modemProxy.getBasebandVersion(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MODEM_SERVICE, "getBasebandVersion", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "getBasebandVersion", e);
             }
         }
     }
@@ -2597,7 +2747,7 @@
             try {
                 voiceProxy.setMute(rr.mSerial, enableMute);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "setMute", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "setMute", e);
             }
         }
     }
@@ -2615,7 +2765,7 @@
             try {
                 voiceProxy.getMute(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "getMute", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getMute", e);
             }
         }
     }
@@ -2633,7 +2783,7 @@
             try {
                 voiceProxy.getClip(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "getClip", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getClip", e);
             }
         }
     }
@@ -2661,7 +2811,7 @@
             try {
                 dataProxy.getDataCallList(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(DATA_SERVICE, "getDataCallList", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "getDataCallList", e);
             }
         }
     }
@@ -2692,7 +2842,8 @@
             try {
                 networkProxy.setSuppServiceNotifications(rr.mSerial, enable);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setSuppServiceNotifications", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK,
+                        "setSuppServiceNotifications", e);
             }
         }
     }
@@ -2715,7 +2866,7 @@
                         RILUtils.convertNullToEmptyString(smsc),
                         RILUtils.convertNullToEmptyString(pdu));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "writeSmsToSim", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "writeSmsToSim", e);
             }
         }
     }
@@ -2736,7 +2887,7 @@
             try {
                 messagingProxy.deleteSmsOnSim(rr.mSerial, index);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "deleteSmsOnSim", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "deleteSmsOnSim", e);
             }
         }
     }
@@ -2755,7 +2906,7 @@
             try {
                 networkProxy.setBandMode(rr.mSerial, bandMode);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setBandMode", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setBandMode", e);
             }
         }
     }
@@ -2774,7 +2925,7 @@
             try {
                 networkProxy.getAvailableBandModes(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "queryAvailableBandMode", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "queryAvailableBandMode", e);
             }
         }
     }
@@ -2794,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);
             }
         }
     }
@@ -2816,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);
             }
         }
     }
@@ -2837,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);
             }
         }
     }
@@ -2856,7 +3007,7 @@
             try {
                 voiceProxy.explicitCallTransfer(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "explicitCallTransfer", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "explicitCallTransfer", e);
             }
         }
     }
@@ -2878,7 +3029,7 @@
             try {
                 networkProxy.setPreferredNetworkTypeBitmap(rr.mSerial, mAllowedNetworkTypesBitmask);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setPreferredNetworkType", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setPreferredNetworkType", e);
             }
         }
     }
@@ -2897,7 +3048,7 @@
             try {
                 networkProxy.getAllowedNetworkTypesBitmap(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getPreferredNetworkType", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getPreferredNetworkType", e);
             }
         }
     }
@@ -2907,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);
@@ -2924,7 +3075,8 @@
             try {
                 networkProxy.setAllowedNetworkTypesBitmap(rr.mSerial, mAllowedNetworkTypesBitmask);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setAllowedNetworkTypeBitmask", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK,
+                        "setAllowedNetworkTypeBitmask", e);
             }
         }
     }
@@ -2943,7 +3095,8 @@
             try {
                 networkProxy.getAllowedNetworkTypesBitmap(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getAllowedNetworkTypeBitmask", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK,
+                        "getAllowedNetworkTypeBitmask", e);
             }
         }
     }
@@ -2963,7 +3116,7 @@
             try {
                 networkProxy.setLocationUpdates(rr.mSerial, enable);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setLocationUpdates", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setLocationUpdates", e);
             }
         }
     }
@@ -2975,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));
 
@@ -2986,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) {
@@ -3016,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));
 
@@ -3028,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");
@@ -3054,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,
@@ -3067,7 +3221,7 @@
                 try {
                     voiceProxy.isVoNrEnabled(rr.mSerial);
                 } catch (RemoteException | RuntimeException e) {
-                    handleRadioProxyExceptionForRR(VOICE_SERVICE, "isVoNrEnabled", e);
+                    handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "isVoNrEnabled", e);
                 }
             }
         } else {
@@ -3087,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,
@@ -3100,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 {
@@ -3132,7 +3286,7 @@
             try {
                 simProxy.setCdmaSubscriptionSource(rr.mSerial, cdmaSubscription);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "setCdmaSubscriptionSource", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "setCdmaSubscriptionSource", e);
             }
         }
     }
@@ -3151,7 +3305,8 @@
             try {
                 networkProxy.getCdmaRoamingPreference(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "queryCdmaRoamingPreference", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK,
+                        "queryCdmaRoamingPreference", e);
             }
         }
     }
@@ -3171,7 +3326,7 @@
             try {
                 networkProxy.setCdmaRoamingPreference(rr.mSerial, cdmaRoamingType);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setCdmaRoamingPreference", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setCdmaRoamingPreference", e);
             }
         }
     }
@@ -3190,7 +3345,7 @@
             try {
                 voiceProxy.getTtyMode(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "getTtyMode", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getTtyMode", e);
             }
         }
     }
@@ -3209,7 +3364,7 @@
             try {
                 voiceProxy.setTtyMode(rr.mSerial, ttyMode);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "setTtyMode", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "setTtyMode", e);
             }
         }
     }
@@ -3229,7 +3384,7 @@
             try {
                 voiceProxy.setPreferredVoicePrivacy(rr.mSerial, enable);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "setPreferredVoicePrivacy", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "setPreferredVoicePrivacy", e);
             }
         }
     }
@@ -3248,7 +3403,7 @@
             try {
                 voiceProxy.getPreferredVoicePrivacy(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "getPreferredVoicePrivacy", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "getPreferredVoicePrivacy", e);
             }
         }
     }
@@ -3268,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);
             }
         }
     }
@@ -3289,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);
             }
         }
     }
@@ -3309,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);
             }
         }
     }
@@ -3337,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);
             }
         }
     }
@@ -3358,7 +3513,7 @@
             try {
                 messagingProxy.acknowledgeLastIncomingCdmaSms(rr.mSerial, success, cause);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE,
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING,
                         "acknowledgeLastIncomingCdmaSms", e);
             }
         }
@@ -3379,7 +3534,7 @@
             try {
                 messagingProxy.getGsmBroadcastConfig(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "getGsmBroadcastConfig", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "getGsmBroadcastConfig", e);
             }
         }
     }
@@ -3403,7 +3558,7 @@
             try {
                 messagingProxy.setGsmBroadcastConfig(rr.mSerial, config);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "setGsmBroadcastConfig", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "setGsmBroadcastConfig", e);
             }
         }
     }
@@ -3424,7 +3579,8 @@
             try {
                 messagingProxy.setGsmBroadcastActivation(rr.mSerial, activate);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "setGsmBroadcastActivation", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING,
+                        "setGsmBroadcastActivation", e);
             }
         }
     }
@@ -3444,7 +3600,7 @@
             try {
                 messagingProxy.getCdmaBroadcastConfig(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "getCdmaBroadcastConfig", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "getCdmaBroadcastConfig", e);
             }
         }
     }
@@ -3468,7 +3624,7 @@
             try {
                 messagingProxy.setCdmaBroadcastConfig(rr.mSerial, configs);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "setCdmaBroadcastConfig", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "setCdmaBroadcastConfig", e);
             }
         }
     }
@@ -3489,7 +3645,8 @@
             try {
                 messagingProxy.setCdmaBroadcastActivation(rr.mSerial, activate);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "setCdmaBroadcastActivation", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING,
+                        "setCdmaBroadcastActivation", e);
             }
         }
     }
@@ -3508,7 +3665,7 @@
             try {
                 simProxy.getCdmaSubscription(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "getCdmaSubscription", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "getCdmaSubscription", e);
             }
         }
     }
@@ -3529,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);
             }
         }
     }
@@ -3550,7 +3707,7 @@
             try {
                 messagingProxy.deleteSmsOnRuim(rr.mSerial, index);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "deleteSmsOnRuim", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "deleteSmsOnRuim", e);
             }
         }
     }
@@ -3569,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();
             }
         }
     }
@@ -3588,7 +3779,7 @@
             try {
                 voiceProxy.exitEmergencyCallbackMode(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(VOICE_SERVICE, "exitEmergencyCallbackMode", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_VOICE, "exitEmergencyCallbackMode", e);
             }
         }
     }
@@ -3608,7 +3799,7 @@
             try {
                 messagingProxy.getSmscAddress(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "getSmscAddress", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "getSmscAddress", e);
             }
         }
     }
@@ -3630,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);
             }
         }
     }
@@ -3651,7 +3842,7 @@
             try {
                 messagingProxy.reportSmsMemoryStatus(rr.mSerial, available);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE, "reportSmsMemoryStatus", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING, "reportSmsMemoryStatus", e);
             }
         }
     }
@@ -3670,7 +3861,7 @@
             try {
                 simProxy.reportStkServiceIsRunning(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "reportStkServiceIsRunning", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "reportStkServiceIsRunning", e);
             }
         }
     }
@@ -3689,7 +3880,7 @@
             try {
                 simProxy.getCdmaSubscriptionSource(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "getCdmaSubscriptionSource", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "getCdmaSubscriptionSource", e);
             }
         }
     }
@@ -3711,7 +3902,7 @@
                 messagingProxy.acknowledgeIncomingGsmSmsWithPdu(rr.mSerial, success,
                         RILUtils.convertNullToEmptyString(ackPdu));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MESSAGING_SERVICE,
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MESSAGING,
                         "acknowledgeIncomingGsmSmsWithPdu", e);
             }
         }
@@ -3731,7 +3922,7 @@
             try {
                 networkProxy.getVoiceRadioTechnology(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getVoiceRadioTechnology", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getVoiceRadioTechnology", e);
             }
         }
     }
@@ -3750,7 +3941,7 @@
             try {
                 networkProxy.getCellInfoList(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getCellInfoList", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getCellInfoList", e);
             }
         }
     }
@@ -3770,7 +3961,7 @@
             try {
                 networkProxy.setCellInfoListRate(rr.mSerial, rateInMillis);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setCellInfoListRate", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setCellInfoListRate", e);
             }
         }
     }
@@ -3790,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);
             }
         }
     }
@@ -3809,7 +4000,7 @@
             try {
                 networkProxy.getImsRegistrationState(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "getImsRegistrationState", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "getImsRegistrationState", e);
             }
         }
     }
@@ -3832,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);
             }
         }
     }
@@ -3854,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);
             }
         }
     }
@@ -3882,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);
             }
         }
     }
@@ -3907,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,
@@ -3921,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);
@@ -3951,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));
@@ -3959,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);
             }
         }
     }
@@ -3981,7 +4172,7 @@
             try {
                 modemProxy.nvReadItem(rr.mSerial, itemID);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MODEM_SERVICE, "nvReadItem", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "nvReadItem", e);
             }
         }
     }
@@ -4002,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);
             }
         }
     }
@@ -4023,7 +4214,7 @@
             try {
                 modemProxy.nvWriteCdmaPrl(rr.mSerial, preferredRoamingList);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MODEM_SERVICE, "nvWriteCdmaPrl", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "nvWriteCdmaPrl", e);
             }
         }
     }
@@ -4043,7 +4234,7 @@
             try {
                 modemProxy.nvResetConfig(rr.mSerial, resetType);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MODEM_SERVICE, "nvResetConfig", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "nvResetConfig", e);
             }
         }
     }
@@ -4065,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);
             }
         }
     }
@@ -4080,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
@@ -4097,7 +4288,7 @@
             try {
                 dataProxy.setDataAllowed(rr.mSerial, allowed);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(DATA_SERVICE, "setDataAllowed", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_DATA, "setDataAllowed", e);
             }
         }
     }
@@ -4117,7 +4308,7 @@
             try {
                 modemProxy.getHardwareConfig(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MODEM_SERVICE, "getHardwareConfig", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "getHardwareConfig", e);
             }
         }
     }
@@ -4140,7 +4331,7 @@
                         RILUtils.convertNullToEmptyString(data),
                         RILUtils.convertNullToEmptyString(aid));
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "requestIccSimAuthentication", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "requestIccSimAuthentication", e);
             }
         }
     }
@@ -4163,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);
             }
         }
     }
@@ -4181,7 +4372,7 @@
             try {
                 modemProxy.requestShutdown(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MODEM_SERVICE, "requestShutdown", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "requestShutdown", e);
             }
         }
     }
@@ -4200,7 +4391,7 @@
             try {
                 modemProxy.getRadioCapability(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(MODEM_SERVICE, "getRadioCapability", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_MODEM, "getRadioCapability", e);
             }
         }
     }
@@ -4220,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");
@@ -4251,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");
@@ -4281,7 +4472,7 @@
             try {
                 radioProxy.stopLceService(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(RADIO_SERVICE, "stopLceService", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_RADIO, "stopLceService", e);
             }
         }
     }
@@ -4300,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));
 
@@ -4315,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");
@@ -4340,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");
@@ -4363,7 +4554,7 @@
             try {
                 radioProxy.pullLceData(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(RADIO_SERVICE, "pullLceData", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_RADIO, "pullLceData", e);
             }
         }
     }
@@ -4385,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);
             }
         }
     }
@@ -4408,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);
             }
         }
     }
@@ -4427,7 +4618,7 @@
             try {
                 simProxy.getAllowedCarriers(rr.mSerial);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(SIM_SERVICE, "getAllowedCarriers", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_SIM, "getAllowedCarriers", e);
             }
         }
     }
@@ -4447,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);
             }
         }
     }
@@ -4467,7 +4658,7 @@
             try {
                 networkProxy.setIndicationFilter(rr.mSerial, filter);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE, "setIndicationFilter", e);
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK, "setIndicationFilter", e);
             }
         }
     }
@@ -4477,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);
 
@@ -4488,7 +4679,7 @@
             try {
                 networkProxy.setSignalStrengthReportingCriteria(rr.mSerial, signalThresholdInfos);
             } catch (RemoteException | RuntimeException e) {
-                handleRadioProxyExceptionForRR(NETWORK_SERVICE,
+                handleRadioProxyExceptionForRR(HAL_SERVICE_NETWORK,
                         "setSignalStrengthReportingCriteria", e);
             }
         } else {
@@ -4502,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);
 
@@ -4516,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");
@@ -4538,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);
             }
         }
     }
@@ -4549,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) {
@@ -4559,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) {
@@ -4579,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);
 
@@ -4590,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");
@@ -4606,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);
 
@@ -4617,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");
@@ -4666,18 +4858,19 @@
     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);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+                riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                        + " " + enable);
             }
 
             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");
@@ -4698,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);
 
@@ -4709,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) {
@@ -4729,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
@@ -4755,7 +4948,7 @@
                 voiceProxy.handleStkCallSetupRequestFromSim(rr.mSerial, accept);
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(
-                        VOICE_SERVICE, "handleStkCallSetupRequestFromSim", e);
+                        HAL_SERVICE_VOICE, "handleStkCallSetupRequestFromSim", e);
             }
         }
     }
@@ -4767,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);
 
@@ -4778,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");
@@ -4797,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) {
@@ -4807,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,
@@ -4823,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) {
@@ -4833,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,
@@ -4849,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) {
@@ -4859,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");
@@ -4878,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) {
@@ -4888,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");
@@ -4905,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);
 
@@ -4916,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");
@@ -4930,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);
 
@@ -4941,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) {
@@ -4959,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);
 
@@ -4970,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) {
@@ -4988,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);
 
@@ -5000,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) {
@@ -5025,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);
 
@@ -5036,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) {
@@ -5059,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);
 
@@ -5070,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) {
@@ -5084,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
@@ -5125,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);
     }
 
@@ -5139,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);
     }
 
@@ -5151,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,
@@ -5186,6 +6155,7 @@
                     + " ,error: " + error);
             return null;
         }
+        Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_NETWORK, "RIL", rr.mSerial);
 
         // Time logging for RIL command and storing it in TelephonyHistogram.
         addToRilHistogram(rr);
@@ -5350,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 {
@@ -5501,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();
@@ -5674,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
@@ -5732,43 +6718,43 @@
         int response = RIL_UNSOL_CDMA_INFO_REC;
         if (infoRec.record instanceof CdmaInformationRecords.CdmaDisplayInfoRec) {
             if (mDisplayInfoRegistrants != null) {
-                if (RILJ_LOGD) unsljLogRet(response, infoRec.record);
+                if (isLogOrTrace()) unsljLogRet(response, infoRec.record);
                 mDisplayInfoRegistrants.notifyRegistrants(
                         new AsyncResult(null, infoRec.record, null));
             }
         } else if (infoRec.record instanceof CdmaInformationRecords.CdmaSignalInfoRec) {
             if (mSignalInfoRegistrants != null) {
-                if (RILJ_LOGD) unsljLogRet(response, infoRec.record);
+                if (isLogOrTrace()) unsljLogRet(response, infoRec.record);
                 mSignalInfoRegistrants.notifyRegistrants(
                         new AsyncResult(null, infoRec.record, null));
             }
         } else if (infoRec.record instanceof CdmaInformationRecords.CdmaNumberInfoRec) {
             if (mNumberInfoRegistrants != null) {
-                if (RILJ_LOGD) unsljLogRet(response, infoRec.record);
+                if (isLogOrTrace()) unsljLogRet(response, infoRec.record);
                 mNumberInfoRegistrants.notifyRegistrants(
                         new AsyncResult(null, infoRec.record, null));
             }
         } else if (infoRec.record instanceof CdmaInformationRecords.CdmaRedirectingNumberInfoRec) {
             if (mRedirNumInfoRegistrants != null) {
-                if (RILJ_LOGD) unsljLogRet(response, infoRec.record);
+                if (isLogOrTrace()) unsljLogRet(response, infoRec.record);
                 mRedirNumInfoRegistrants.notifyRegistrants(
                         new AsyncResult(null, infoRec.record, null));
             }
         } else if (infoRec.record instanceof CdmaInformationRecords.CdmaLineControlInfoRec) {
             if (mLineControlInfoRegistrants != null) {
-                if (RILJ_LOGD) unsljLogRet(response, infoRec.record);
+                if (isLogOrTrace()) unsljLogRet(response, infoRec.record);
                 mLineControlInfoRegistrants.notifyRegistrants(
                         new AsyncResult(null, infoRec.record, null));
             }
         } else if (infoRec.record instanceof CdmaInformationRecords.CdmaT53ClirInfoRec) {
             if (mT53ClirInfoRegistrants != null) {
-                if (RILJ_LOGD) unsljLogRet(response, infoRec.record);
+                if (isLogOrTrace()) unsljLogRet(response, infoRec.record);
                 mT53ClirInfoRegistrants.notifyRegistrants(
                         new AsyncResult(null, infoRec.record, null));
             }
         } else if (infoRec.record instanceof CdmaInformationRecords.CdmaT53AudioControlInfoRec) {
             if (mT53AudCntrlInfoRegistrants != null) {
-                if (RILJ_LOGD) {
+                if (isLogOrTrace()) {
                     unsljLogRet(response, infoRec.record);
                 }
                 mT53AudCntrlInfoRegistrants.notifyRegistrants(
@@ -5790,26 +6776,48 @@
         Rlog.v(RILJ_LOG_TAG, msg + (" [PHONE" + mPhoneId + "]"));
     }
 
+    boolean isLogOrTrace() {
+        return RIL.RILJ_LOGD || Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK);
+    }
+
+    boolean isLogvOrTrace() {
+        return RIL.RILJ_LOGV || Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK);
+    }
+
     @UnsupportedAppUsage
     void unsljLog(int response) {
-        riljLog("[UNSL]< " + RILUtils.responseToString(response));
+        String logStr = RILUtils.responseToString(response);
+        if (RIL.RILJ_LOGD) {
+            riljLog("[UNSL]< " + logStr);
+        }
+        Trace.instantForTrack(Trace.TRACE_TAG_NETWORK, "RIL", logStr);
     }
 
     @UnsupportedAppUsage
     void unsljLogMore(int response, String more) {
-        riljLog("[UNSL]< " + RILUtils.responseToString(response) + " " + more);
+        String logStr = RILUtils.responseToString(response) + " " + more;
+        if (RIL.RILJ_LOGD) {
+            riljLog("[UNSL]< " + logStr);
+        }
+        Trace.instantForTrack(Trace.TRACE_TAG_NETWORK, "RIL", logStr);
     }
 
     @UnsupportedAppUsage
     void unsljLogRet(int response, Object ret) {
-        riljLog("[UNSL]< " + RILUtils.responseToString(response) + " "
-                + retToString(response, ret));
+        String logStr = RILUtils.responseToString(response) + " " + retToString(response, ret);
+        if (RIL.RILJ_LOGD) {
+            riljLog("[UNSL]< " + logStr);
+        }
+        Trace.instantForTrack(Trace.TRACE_TAG_NETWORK, "RIL", logStr);
     }
 
     @UnsupportedAppUsage
     void unsljLogvRet(int response, Object ret) {
-        riljLogv("[UNSL]< " + RILUtils.responseToString(response) + " "
-                + retToString(response, ret));
+        String logStr = RILUtils.responseToString(response) + " " + retToString(response, ret);
+        if (RIL.RILJ_LOGV) {
+            riljLogv("[UNSL]< " + logStr);
+        }
+        Trace.instantForTrack(Trace.TRACE_TAG_NETWORK, "RIL", logStr);
     }
 
     @Override
@@ -5829,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) {
@@ -5904,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 9ac03b1..3e2be1d 100644
--- a/src/java/com/android/internal/telephony/RadioConfig.java
+++ b/src/java/com/android/internal/telephony/RadioConfig.java
@@ -36,6 +36,7 @@
 import android.os.Registrant;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.Trace;
 import android.os.WorkSource;
 import android.telephony.TelephonyManager;
 import android.telephony.UiccSlotMapping;
@@ -44,7 +45,6 @@
 import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.concurrent.atomic.AtomicLong;
@@ -342,6 +342,9 @@
 
     private RILRequest obtainRequest(int request, Message result, WorkSource workSource) {
         RILRequest rr = RILRequest.obtain(request, result, workSource);
+        Trace.asyncTraceForTrackBegin(
+                Trace.TRACE_TAG_NETWORK, "RIL", RILUtils.requestToString(rr.mRequest), rr.mSerial);
+
         synchronized (mRequestList) {
             mRequestList.append(rr.mSerial, rr);
         }
@@ -352,7 +355,10 @@
         RILRequest rr;
         synchronized (mRequestList) {
             rr = mRequestList.get(serial);
+
             if (rr != null) {
+                Trace.asyncTraceForTrackEnd(
+                        Trace.TRACE_TAG_NETWORK, "RIL", rr.mSerial);
                 mRequestList.remove(serial);
             }
         }
@@ -636,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/RadioConfigIndicationAidl.java b/src/java/com/android/internal/telephony/RadioConfigIndicationAidl.java
index 851a8ef..76e6d5e 100644
--- a/src/java/com/android/internal/telephony/RadioConfigIndicationAidl.java
+++ b/src/java/com/android/internal/telephony/RadioConfigIndicationAidl.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony;
 
 import android.os.AsyncResult;
+import android.os.Trace;
 
 import com.android.internal.telephony.uicc.IccSlotStatus;
 import com.android.telephony.Rlog;
@@ -43,7 +44,7 @@
     public void simSlotsStatusChanged(
             int type, android.hardware.radio.config.SimSlotStatus[] slotStatus) {
         ArrayList<IccSlotStatus> ret = RILUtils.convertHalSlotStatus(slotStatus);
-        logd("[UNSL]< UNSOL_SIM_SLOT_STATUS_CHANGED " + ret.toString());
+        logd("UNSOL_SIM_SLOT_STATUS_CHANGED " + ret.toString());
         if (mRadioConfig.mSimSlotStatusRegistrant != null) {
             mRadioConfig.mSimSlotStatusRegistrant.notifyRegistrant(
                     new AsyncResult(null, ret, null));
@@ -51,7 +52,8 @@
     }
 
     private static void logd(String log) {
-        Rlog.d(TAG, log);
+        Rlog.d(TAG, "[UNSL]< " + log);
+        Trace.instantForTrack(Trace.TRACE_TAG_NETWORK, "RIL", log);
     }
 
     @Override
diff --git a/src/java/com/android/internal/telephony/RadioConfigIndicationHidl.java b/src/java/com/android/internal/telephony/RadioConfigIndicationHidl.java
index 23a676b..3b5330b 100644
--- a/src/java/com/android/internal/telephony/RadioConfigIndicationHidl.java
+++ b/src/java/com/android/internal/telephony/RadioConfigIndicationHidl.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony;
 
 import android.os.AsyncResult;
+import android.os.Trace;
 
 import com.android.internal.telephony.uicc.IccSlotStatus;
 import com.android.telephony.Rlog;
@@ -42,7 +43,7 @@
     public void simSlotsStatusChanged(int indicationType,
             ArrayList<android.hardware.radio.config.V1_0.SimSlotStatus> slotStatus) {
         ArrayList<IccSlotStatus> ret = RILUtils.convertHalSlotStatus(slotStatus);
-        logd("[UNSL]< UNSOL_SIM_SLOT_STATUS_CHANGED " + ret.toString());
+        logd("UNSOL_SIM_SLOT_STATUS_CHANGED " + ret.toString());
         if (mRadioConfig.mSimSlotStatusRegistrant != null) {
             mRadioConfig.mSimSlotStatusRegistrant.notifyRegistrant(
                     new AsyncResult(null, ret, null));
@@ -55,7 +56,7 @@
     public void simSlotsStatusChanged_1_2(int indicationType,
             ArrayList<android.hardware.radio.config.V1_2.SimSlotStatus> slotStatus) {
         ArrayList<IccSlotStatus> ret = RILUtils.convertHalSlotStatus(slotStatus);
-        logd("[UNSL]< UNSOL_SIM_SLOT_STATUS_CHANGED " + ret.toString());
+        logd("UNSOL_SIM_SLOT_STATUS_CHANGED " + ret.toString());
         if (mRadioConfig.mSimSlotStatusRegistrant != null) {
             mRadioConfig.mSimSlotStatusRegistrant.notifyRegistrant(
                     new AsyncResult(null, ret, null));
@@ -63,6 +64,7 @@
     }
 
     private static void logd(String log) {
-        Rlog.d(TAG, log);
+        Rlog.d(TAG, "[UNSL]< " + log);
+        Trace.instantForTrack(Trace.TRACE_TAG_NETWORK, "RIL", log);
     }
 }
diff --git a/src/java/com/android/internal/telephony/RadioConfigProxy.java b/src/java/com/android/internal/telephony/RadioConfigProxy.java
index 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 c7244be..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,21 +136,21 @@
      * @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 (RIL.RILJ_LOGD) {
-            mRil.unsljLogMore(RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED, "radioStateChanged: " +
-                    state);
+        if (mRil.isLogOrTrace()) {
+            mRil.unsljLogMore(
+                    RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED, "radioStateChanged: " + state);
         }
 
         mRil.setRadioState(state, false /* forceNotifyRegistrants */);
     }
 
     public void callStateChanged(int indicationType) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED);
 
         mRil.mCallStateRegistrants.notifyRegistrants();
     }
@@ -160,18 +160,18 @@
      * @param indicationType RadioIndicationType
      */
     public void networkStateChanged(int indicationType) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESPONSE_NETWORK_STATE_CHANGED);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_NETWORK_STATE_CHANGED);
 
         mRil.mNetworkStateRegistrants.notifyRegistrants();
     }
 
     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 (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESPONSE_NEW_SMS);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_NEW_SMS);
 
         SmsMessageBase smsb = com.android.internal.telephony.gsm.SmsMessage.createFromPdu(pduArray);
         if (mRil.mGsmSmsRegistrant != null) {
@@ -181,10 +181,10 @@
     }
 
     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 (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT);
 
         if (mRil.mSmsStatusRegistrant != null) {
             mRil.mSmsStatusRegistrant.notifyRegistrant(new AsyncResult(null, pduArray, null));
@@ -192,9 +192,9 @@
     }
 
     public void newSmsOnSim(int indicationType, int recordNumber) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESPONSE_NEW_SMS_ON_SIM);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_NEW_SMS_ON_SIM);
 
         if (mRil.mSmsOnSimRegistrant != null) {
             mRil.mSmsOnSimRegistrant.notifyRegistrant(new AsyncResult(null, recordNumber, null));
@@ -202,9 +202,9 @@
     }
 
     public void onUssd(int indicationType, int ussdModeType, String msg) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogMore(RIL_UNSOL_ON_USSD, "" + ussdModeType);
+        if (mRil.isLogOrTrace()) mRil.unsljLogMore(RIL_UNSOL_ON_USSD, "" + ussdModeType);
 
         // todo: Clean this up with a parcelable class for better self-documentation
         String[] resp = new String[2];
@@ -216,9 +216,9 @@
     }
 
     public void nitzTimeReceived(int indicationType, String nitzTime, long receivedTime) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_NITZ_TIME_RECEIVED, nitzTime);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_NITZ_TIME_RECEIVED, nitzTime);
 
         // todo: Clean this up with a parcelable class for better self-documentation
         Object[] result = new Object[2];
@@ -228,7 +228,7 @@
         boolean ignoreNitz = TelephonyProperties.ignore_nitz().orElse(false);
 
         if (ignoreNitz) {
-            if (RIL.RILJ_LOGD) mRil.riljLog("ignoring UNSOL_NITZ_TIME_RECEIVED");
+            if (mRil.isLogOrTrace()) mRil.riljLog("ignoring UNSOL_NITZ_TIME_RECEIVED");
         } else {
             if (mRil.mNITZTimeRegistrant != null) {
                 mRil.mNITZTimeRegistrant.notifyRegistrant(new AsyncResult (null, result, null));
@@ -241,13 +241,13 @@
 
     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);
 
         SignalStrength ss = mRil.fixupSignalStrength10(ssInitial);
         // Note this is set to "verbose" because it happens frequently
-        if (RIL.RILJ_LOGV) mRil.unsljLogvRet(RIL_UNSOL_SIGNAL_STRENGTH, ss);
+        if (mRil.isLogvOrTrace()) mRil.unsljLogvRet(RIL_UNSOL_SIGNAL_STRENGTH, ss);
 
         if (mRil.mSignalStrengthRegistrant != null) {
             mRil.mSignalStrengthRegistrant.notifyRegistrant(new AsyncResult (null, ss, null));
@@ -259,11 +259,11 @@
      */
     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);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_LCEDATA_RECV, response);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_LCEDATA_RECV, response);
 
         if (mRil.mLceInfoRegistrants != null) {
             mRil.mLceInfoRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
@@ -275,11 +275,11 @@
      */
     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);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_LCEDATA_RECV, response);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_LCEDATA_RECV, response);
 
         if (mRil.mLceInfoRegistrants != null) {
             mRil.mLceInfoRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
@@ -291,11 +291,11 @@
      */
     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
-        if (RIL.RILJ_LOGV) mRil.unsljLogvRet(RIL_UNSOL_SIGNAL_STRENGTH, ss);
+        if (mRil.isLogvOrTrace()) mRil.unsljLogvRet(RIL_UNSOL_SIGNAL_STRENGTH, ss);
 
         if (mRil.mSignalStrengthRegistrant != null) {
             mRil.mSignalStrengthRegistrant.notifyRegistrant(new AsyncResult(null, ss, null));
@@ -308,11 +308,11 @@
     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);
 
-        if (RIL.RILJ_LOGV) mRil.unsljLogvRet(RIL_UNSOL_SIGNAL_STRENGTH, ss);
+        if (mRil.isLogvOrTrace()) mRil.unsljLogvRet(RIL_UNSOL_SIGNAL_STRENGTH, ss);
 
         if (mRil.mSignalStrengthRegistrant != null) {
             mRil.mSignalStrengthRegistrant.notifyRegistrant(new AsyncResult(null, ss, null));
@@ -325,11 +325,11 @@
     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);
 
-        if (RIL.RILJ_LOGV) mRil.unsljLogvRet(RIL_UNSOL_SIGNAL_STRENGTH, ss);
+        if (mRil.isLogvOrTrace()) mRil.unsljLogvRet(RIL_UNSOL_SIGNAL_STRENGTH, ss);
 
         if (mRil.mSignalStrengthRegistrant != null) {
             mRil.mSignalStrengthRegistrant.notifyRegistrant(new AsyncResult(null, ss, null));
@@ -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
@@ -380,7 +380,7 @@
             response.add(emergencyNumber);
         }
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_EMERGENCY_NUMBER_LIST, response);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_EMERGENCY_NUMBER_LIST, response);
 
         // Cache emergency number list from last indication.
         mRil.cacheEmergencyNumberListIndication(response);
@@ -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;
@@ -431,7 +431,9 @@
         notification.type = suppSvcNotification.type;
         notification.number = suppSvcNotification.number;
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_SUPP_SVC_NOTIFICATION, notification);
+        if (mRil.isLogOrTrace()) {
+            mRil.unsljLogRet(RIL_UNSOL_SUPP_SVC_NOTIFICATION, notification);
+        }
 
         if (mRil.mSsnRegistrant != null) {
             mRil.mSsnRegistrant.notifyRegistrant(new AsyncResult (null, notification, null));
@@ -439,9 +441,9 @@
     }
 
     public void stkSessionEnd(int indicationType) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_STK_SESSION_END);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_STK_SESSION_END);
 
         if (mRil.mCatSessionEndRegistrant != null) {
             mRil.mCatSessionEndRegistrant.notifyRegistrant(new AsyncResult (null, null, null));
@@ -449,9 +451,9 @@
     }
 
     public void stkProactiveCommand(int indicationType, String cmd) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_STK_PROACTIVE_COMMAND);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_STK_PROACTIVE_COMMAND);
 
         if (mRil.mCatProCmdRegistrant != null) {
             mRil.mCatProCmdRegistrant.notifyRegistrant(new AsyncResult (null, cmd, null));
@@ -459,9 +461,9 @@
     }
 
     public void stkEventNotify(int indicationType, String cmd) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_STK_EVENT_NOTIFY);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_STK_EVENT_NOTIFY);
 
         if (mRil.mCatEventRegistrant != null) {
             mRil.mCatEventRegistrant.notifyRegistrant(new AsyncResult (null, cmd, null));
@@ -469,9 +471,9 @@
     }
 
     public void stkCallSetup(int indicationType, long timeout) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_STK_CALL_SETUP, timeout);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_STK_CALL_SETUP, timeout);
 
         if (mRil.mCatCallSetUpRegistrant != null) {
             mRil.mCatCallSetUpRegistrant.notifyRegistrant(new AsyncResult (null, timeout, null));
@@ -479,9 +481,9 @@
     }
 
     public void simSmsStorageFull(int indicationType) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_SIM_SMS_STORAGE_FULL);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_SIM_SMS_STORAGE_FULL);
 
         if (mRil.mIccSmsFullRegistrant != null) {
             mRil.mIccSmsFullRegistrant.notifyRegistrant();
@@ -489,20 +491,20 @@
     }
 
     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;
         response.efId = refreshResult.efId;
         response.aid = refreshResult.aid;
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_SIM_REFRESH, response);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_SIM_REFRESH, response);
 
         mRil.mIccRefreshRegistrants.notifyRegistrants(new AsyncResult (null, response, null));
     }
 
     public void callRing(int indicationType, boolean isGsm, CdmaSignalInfoRecord record) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         char response[] = null;
 
@@ -517,7 +519,7 @@
             mRil.writeMetricsCallRing(response);
         }
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CALL_RING, response);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_CALL_RING, response);
 
         if (mRil.mRingRegistrant != null) {
             mRil.mRingRegistrant.notifyRegistrant(new AsyncResult (null, response, null));
@@ -525,17 +527,17 @@
     }
 
     public void simStatusChanged(int indicationType) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED);
 
         mRil.mIccStatusChangedRegistrants.notifyRegistrants();
     }
 
     public void cdmaNewSms(int indicationType, CdmaSmsMessage msg) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESPONSE_CDMA_NEW_SMS);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_CDMA_NEW_SMS);
 
         SmsMessage sms = new SmsMessage(RILUtils.convertHalCdmaSmsMessage(msg));
         if (mRil.mCdmaSmsRegistrant != null) {
@@ -544,12 +546,12 @@
     }
 
     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 (RIL.RILJ_LOGD) {
-            mRil.unsljLogvRet(RIL_UNSOL_RESPONSE_NEW_BROADCAST_SMS,
-                    IccUtils.bytesToHexString(response));
+        if (mRil.isLogOrTrace()) {
+            mRil.unsljLogvRet(
+                    RIL_UNSOL_RESPONSE_NEW_BROADCAST_SMS, IccUtils.bytesToHexString(response));
         }
 
         if (mRil.mGsmBroadcastSmsRegistrant != null) {
@@ -558,9 +560,9 @@
     }
 
     public void cdmaRuimSmsStorageFull(int indicationType) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_CDMA_RUIM_SMS_STORAGE_FULL);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_CDMA_RUIM_SMS_STORAGE_FULL);
 
         if (mRil.mIccSmsFullRegistrant != null) {
             mRil.mIccSmsFullRegistrant.notifyRegistrant();
@@ -568,9 +570,9 @@
     }
 
     public void restrictedStateChanged(int indicationType, int state) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogvRet(RIL_UNSOL_RESTRICTED_STATE_CHANGED, state);
+        if (mRil.isLogOrTrace()) mRil.unsljLogvRet(RIL_UNSOL_RESTRICTED_STATE_CHANGED, state);
 
         if (mRil.mRestrictedStateRegistrant != null) {
             mRil.mRestrictedStateRegistrant.notifyRegistrant(new AsyncResult (null, state, null));
@@ -578,9 +580,9 @@
     }
 
     public void enterEmergencyCallbackMode(int indicationType) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_ENTER_EMERGENCY_CALLBACK_MODE);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_ENTER_EMERGENCY_CALLBACK_MODE);
 
         if (mRil.mEmergencyCallbackModeRegistrant != null) {
             mRil.mEmergencyCallbackModeRegistrant.notifyRegistrant();
@@ -588,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
@@ -605,26 +607,28 @@
         notification.numberType = callWaitingRecord.numberType;
         notification.numberPlan = callWaitingRecord.numberPlan;
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CDMA_CALL_WAITING, notification);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_CDMA_CALL_WAITING, notification);
 
         mRil.mCallWaitingInfoRegistrants.notifyRegistrants(
                 new AsyncResult (null, notification, null));
     }
 
     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;
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CDMA_OTA_PROVISION_STATUS, response);
+        if (mRil.isLogOrTrace()) {
+            mRil.unsljLogRet(RIL_UNSOL_CDMA_OTA_PROVISION_STATUS, response);
+        }
 
         mRil.mOtaProvisionRegistrants.notifyRegistrants(new AsyncResult (null, response, null));
     }
 
     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++) {
@@ -712,63 +716,67 @@
                             + CdmaInformationRecords.idToString(id) + " ");
             }
 
-            if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CDMA_INFO_REC, cdmaInformationRecords);
+            if (mRil.isLogOrTrace()) {
+                mRil.unsljLogRet(RIL_UNSOL_CDMA_INFO_REC, cdmaInformationRecords);
+            }
             mRil.notifyRegistrantsCdmaInfoRec(cdmaInformationRecords);
         }
     }
 
     public void indicateRingbackTone(int indicationType, boolean start) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogvRet(RIL_UNSOL_RINGBACK_TONE, start);
+        if (mRil.isLogOrTrace()) mRil.unsljLogvRet(RIL_UNSOL_RINGBACK_TONE, start);
 
         mRil.mRingbackToneRegistrants.notifyRegistrants(new AsyncResult(null, start, null));
     }
 
     public void resendIncallMute(int indicationType) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESEND_INCALL_MUTE);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESEND_INCALL_MUTE);
 
         mRil.mResendIncallMuteRegistrants.notifyRegistrants();
     }
 
     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;
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED, response);
+        if (mRil.isLogOrTrace()) {
+            mRil.unsljLogRet(RIL_UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED, response);
+        }
 
         mRil.mCdmaSubscriptionChangedRegistrants.notifyRegistrants(
                 new AsyncResult (null, response, null));
     }
 
     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;
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CDMA_PRL_CHANGED, response);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_CDMA_PRL_CHANGED, response);
 
         mRil.mCdmaPrlChangedRegistrants.notifyRegistrants(
                 new AsyncResult (null, response, null));
     }
 
     public void exitEmergencyCallbackMode(int indicationType) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_EXIT_EMERGENCY_CALLBACK_MODE);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_EXIT_EMERGENCY_CALLBACK_MODE);
 
         mRil.mExitEmergencyCallbackModeRegistrants.notifyRegistrants();
     }
 
     public void rilConnected(int indicationType) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RIL_CONNECTED);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RIL_CONNECTED);
 
         // Initial conditions
         mRil.setRadioPower(false, null);
@@ -779,12 +787,14 @@
     }
 
     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;
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_VOICE_RADIO_TECH_CHANGED, response);
+        if (mRil.isLogOrTrace()) {
+            mRil.unsljLogRet(RIL_UNSOL_VOICE_RADIO_TECH_CHANGED, response);
+        }
 
         mRil.mVoiceRadioTechChangedRegistrants.notifyRegistrants(
                 new AsyncResult (null, response, null));
@@ -793,49 +803,49 @@
     /** 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);
     }
 
     private void responseCellInfoList(ArrayList<? extends Object> records) {
         ArrayList<CellInfo> response = RILUtils.convertHalCellInfoList((ArrayList<Object>) records);
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CELL_INFO_LIST, response);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_CELL_INFO_LIST, response);
         mRil.mRilCellInfoListRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
     }
 
     /** 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 (RIL.RILJ_LOGD) {
+        if (mRil.isLogOrTrace()) {
             mRil.unsljLogRet(RIL_UNSOL_UICC_APPLICATIONS_ENABLEMENT_CHANGED, enabled);
         }
 
@@ -873,32 +883,34 @@
     }
 
     public void imsNetworkStateChanged(int indicationType) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESPONSE_IMS_NETWORK_STATE_CHANGED);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_IMS_NETWORK_STATE_CHANGED);
 
         mRil.mImsNetworkStateChangedRegistrants.notifyRegistrants();
     }
 
     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;
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_UICC_SUBSCRIPTION_STATUS_CHANGED, response);
+        if (mRil.isLogOrTrace()) {
+            mRil.unsljLogRet(RIL_UNSOL_UICC_SUBSCRIPTION_STATUS_CHANGED, response);
+        }
 
         mRil.mSubscriptionStatusRegistrants.notifyRegistrants(
                 new AsyncResult (null, response, null));
     }
 
     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;
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_SRVCC_STATE_NOTIFY, response);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_SRVCC_STATE_NOTIFY, response);
 
         mRil.writeMetricsSrvcc(state);
 
@@ -909,11 +921,11 @@
     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);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_HARDWARE_CONFIG_CHANGED, response);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_HARDWARE_CONFIG_CHANGED, response);
 
         mRil.mHardwareConfigChangeRegistrants.notifyRegistrants(
                 new AsyncResult (null, response, null));
@@ -921,18 +933,18 @@
 
     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);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_RADIO_CAPABILITY, response);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_RADIO_CAPABILITY, response);
 
         mRil.mPhoneRadioCapabilityChangedRegistrants.notifyRegistrants(
                 new AsyncResult (null, response, null));
     }
 
     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();
@@ -972,7 +984,7 @@
             }
         }
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_ON_SS, ssData);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_ON_SS, ssData);
 
         if (mRil.mSsRegistrant != null) {
             mRil.mSsRegistrant.notifyRegistrant(new AsyncResult(null, ssData, null));
@@ -980,9 +992,9 @@
     }
 
     public void stkCallControlAlphaNotify(int indicationType, String alpha) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_STK_CC_ALPHA_NOTIFY, alpha);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_STK_CC_ALPHA_NOTIFY, alpha);
 
         if (mRil.mCatCcAlphaRegistrant != null) {
             mRil.mCatCcAlphaRegistrant.notifyRegistrant(new AsyncResult (null, alpha, null));
@@ -990,11 +1002,11 @@
     }
 
     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);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_LCEDATA_RECV, response);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_LCEDATA_RECV, response);
 
         if (mRil.mLceInfoRegistrants != null) {
             mRil.mLceInfoRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
@@ -1002,20 +1014,20 @@
     }
 
     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));
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_PCO_DATA, response);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_PCO_DATA, response);
 
         mRil.mPcoDataRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
     }
 
     public void modemReset(int indicationType, String reason) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_MODEM_RESTART, reason);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_MODEM_RESTART, reason);
 
         mRil.writeMetricsModemRestartEvent(reason);
         mRil.mModemResetRegistrants.notifyRegistrants(new AsyncResult(null, reason, null));
@@ -1026,9 +1038,11 @@
      * @param indicationType RadioIndicationType
      */
     public void carrierInfoForImsiEncryption(int indicationType) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION, null);
+        if (mRil.isLogOrTrace()) {
+            mRil.unsljLogRet(RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION, null);
+        }
 
         mRil.mCarrierInfoForImsiEncryptionRegistrants.notifyRegistrants(
                 new AsyncResult(null, null, null));
@@ -1041,11 +1055,12 @@
      */
     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 (RIL.RILJ_LOGD) {
-            mRil.unsljLogRet(RIL_UNSOL_KEEPALIVE_STATUS,
-                    "handle=" + halStatus.sessionHandle + " code=" +  halStatus.code);
+        if (mRil.isLogOrTrace()) {
+            mRil.unsljLogRet(
+                    RIL_UNSOL_KEEPALIVE_STATUS,
+                    "handle=" + halStatus.sessionHandle + " code=" + halStatus.code);
         }
 
         KeepaliveStatus ks = new KeepaliveStatus(
@@ -1059,9 +1074,9 @@
      * @param indicationType RadioIndicationType
      */
     public void simPhonebookChanged(int indicationType) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
-        if (RIL.RILJ_LOGD) {
+        if (mRil.isLogOrTrace()) {
             mRil.unsljLog(RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED);
         }
 
@@ -1076,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<>();
 
@@ -1084,8 +1099,9 @@
             simPhonebookRecords.add(RILUtils.convertHalPhonebookRecordInfo(record));
         }
 
-        if (RIL.RILJ_LOGD) {
-            mRil.unsljLogRet(RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED,
+        if (mRil.isLogOrTrace()) {
+            mRil.unsljLogRet(
+                    RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED,
                     "status = " + status + " received " + records.size() + " records");
         }
 
@@ -1108,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)
@@ -1116,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");
 
@@ -1139,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");
 
@@ -1208,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)
@@ -1240,13 +1263,14 @@
                 }
             }
         } 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;
         }
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_PHYSICAL_CHANNEL_CONFIG, response);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_PHYSICAL_CHANNEL_CONFIG, response);
 
         mRil.mPhysicalChannelConfigurationRegistrants.notifyRegistrants(
                 new AsyncResult(null, response, null));
@@ -1254,63 +1278,63 @@
 
     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));
         NetworkScanResult nsr = new NetworkScanResult(result.status, result.error, cellInfos);
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_NETWORK_SCAN_RESULT, nsr);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_NETWORK_SCAN_RESULT, nsr);
         mRil.mRilNetworkScanResultRegistrants.notifyRegistrants(new AsyncResult(null, nsr, null));
     }
 
     private void responseNetworkScan_1_2(int indicationType,
             android.hardware.radio.V1_2.NetworkScanResult result) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         ArrayList<CellInfo> cellInfos =
                 RILUtils.convertHalCellInfoList(new ArrayList<>(result.networkInfos));
         NetworkScanResult nsr = new NetworkScanResult(result.status, result.error, cellInfos);
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_NETWORK_SCAN_RESULT, nsr);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_NETWORK_SCAN_RESULT, nsr);
         mRil.mRilNetworkScanResultRegistrants.notifyRegistrants(new AsyncResult(null, nsr, null));
     }
 
     private void responseNetworkScan_1_4(int indicationType,
             android.hardware.radio.V1_4.NetworkScanResult result) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
         ArrayList<CellInfo> cellInfos =
                 RILUtils.convertHalCellInfoList(new ArrayList<>(result.networkInfos));
         NetworkScanResult nsr = new NetworkScanResult(result.status, result.error, cellInfos);
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_NETWORK_SCAN_RESULT, nsr);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_NETWORK_SCAN_RESULT, nsr);
         mRil.mRilNetworkScanResultRegistrants.notifyRegistrants(new AsyncResult(null, nsr, null));
     }
 
     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));
         NetworkScanResult nsr = new NetworkScanResult(result.status, result.error, cellInfos);
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_NETWORK_SCAN_RESULT, nsr);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_NETWORK_SCAN_RESULT, nsr);
         mRil.mRilNetworkScanResultRegistrants.notifyRegistrants(new AsyncResult(null, nsr, null));
     }
 
     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));
         NetworkScanResult nsr = new NetworkScanResult(result.status, result.error, cellInfos);
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_NETWORK_SCAN_RESULT, nsr);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_NETWORK_SCAN_RESULT, nsr);
         mRil.mRilNetworkScanResultRegistrants.notifyRegistrants(new AsyncResult(null, nsr, null));
     }
 
     private void responseDataCallListChanged(int indicationType, List<?> dcList) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_DATA_CALL_LIST_CHANGED, dcList);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_DATA_CALL_LIST_CHANGED, dcList);
 
         ArrayList<DataCallResponse> response = RILUtils.convertHalDataCallResultList(dcList);
         mRil.mDataCallListChangedRegistrants.notifyRegistrants(
@@ -1318,17 +1342,11 @@
     }
 
     private void responseApnUnthrottled(int indicationType, String apn) {
-        mRil.processIndication(RIL.RADIO_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_RADIO, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_UNTHROTTLE_APN, apn);
+        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/RadioResponse.java b/src/java/com/android/internal/telephony/RadioResponse.java
index 5265be5..0bc2958 100644
--- a/src/java/com/android/internal/telephony/RadioResponse.java
+++ b/src/java/com/android/internal/telephony/RadioResponse.java
@@ -2532,10 +2532,9 @@
             ArrayList<NeighboringCellInfo> ret = new ArrayList<NeighboringCellInfo>();
             NeighboringCellInfo cell;
 
-            int[] subId = SubscriptionManager.getSubId(mRil.mPhoneId);
-            int radioType =
-                    ((TelephonyManager) mRil.mContext.getSystemService(
-                            Context.TELEPHONY_SERVICE)).getDataNetworkType(subId[0]);
+            int radioType = ((TelephonyManager) mRil.mContext.getSystemService(
+                    Context.TELEPHONY_SERVICE)).getDataNetworkType(
+                            SubscriptionManager.getSubscriptionId(mRil.mPhoneId));
 
             if (radioType != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
                 for (int i = 0; i < cells.size(); i++) {
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 c7e19c4..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));
@@ -576,7 +616,7 @@
                 halImsiInfo.base.expirationTime = imsiEncryptionInfo.getExpirationTime().getTime();
             }
             for (byte b : imsiEncryptionInfo.getPublicKey().getEncoded()) {
-                halImsiInfo.base.carrierKey.add(new Byte(b));
+                halImsiInfo.base.carrierKey.add(Byte.valueOf(b));
             }
             halImsiInfo.keyType = (byte) imsiEncryptionInfo.getKeyType();
 
@@ -592,7 +632,7 @@
                 halImsiInfo.expirationTime = imsiEncryptionInfo.getExpirationTime().getTime();
             }
             for (byte b : imsiEncryptionInfo.getPublicKey().getEncoded()) {
-                halImsiInfo.carrierKey.add(new Byte(b));
+                halImsiInfo.carrierKey.add(Byte.valueOf(b));
             }
 
             ((android.hardware.radio.V1_1.IRadio) mRadioProxy).setCarrierInfoForImsiEncryption(
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 24a8ac5..aff62ae 100644
--- a/src/java/com/android/internal/telephony/RatRatcheter.java
+++ b/src/java/com/android/internal/telephony/RatRatcheter.java
@@ -16,13 +16,7 @@
 package com.android.internal.telephony;
 
 import android.annotation.NonNull;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
 import android.os.PersistableBundle;
-import android.os.UserHandle;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.Annotation.NetworkType;
 import android.telephony.CarrierConfigManager;
@@ -80,16 +74,11 @@
     /** Constructor */
     public RatRatcheter(Phone phone) {
         mPhone = phone;
-
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        try {
-            Context contextAsUser = phone.getContext().createPackageContextAsUser(
-                phone.getContext().getPackageName(), 0, UserHandle.ALL);
-            contextAsUser.registerReceiver(mConfigChangedReceiver,
-                intentFilter, null /* broadcastPermission */, null);
-        } catch (PackageManager.NameNotFoundException e) {
-            Rlog.e(LOG_TAG, "Package name not found: " + e.getMessage());
+        CarrierConfigManager ccm = mPhone.getContext().getSystemService(CarrierConfigManager.class);
+        if (ccm != null) {
+            ccm.registerCarrierConfigChangeListener(
+                    mPhone.getContext().getMainExecutor(),
+                    (slotIndex, subId, carrierId, specificCarrierId) -> resetRatFamilyMap());
         }
         resetRatFamilyMap();
     }
@@ -152,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
@@ -183,25 +181,16 @@
         }
     }
 
-    private BroadcastReceiver mConfigChangedReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(action)) {
-                resetRatFamilyMap();
-            }
-        }
-    };
-
     private void resetRatFamilyMap() {
         synchronized(mRatFamilyMap) {
             mRatFamilyMap.clear();
 
-            final CarrierConfigManager configManager = (CarrierConfigManager)
-                    mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
-            if (configManager == null) return;
-            PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId());
-            if (b == null) return;
+            PersistableBundle b =
+                    CarrierConfigManager.getCarrierConfigSubset(
+                            mPhone.getContext(),
+                            mPhone.getSubId(),
+                            CarrierConfigManager.KEY_RATCHET_RAT_FAMILIES);
+            if (b == null || b.isEmpty()) return;
 
             // Reads an array of strings, eg:
             // ["GPRS, EDGE", "EVDO, EVDO_A, EVDO_B", "HSPA, HSDPA, HSUPA, HSPAP"]
diff --git a/src/java/com/android/internal/telephony/RetryManager.java b/src/java/com/android/internal/telephony/RetryManager.java
deleted file mode 100644
index 83864e4..0000000
--- a/src/java/com/android/internal/telephony/RetryManager.java
+++ /dev/null
@@ -1,723 +0,0 @@
-/**
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.telephony;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Context;
-import android.os.Build;
-import android.os.PersistableBundle;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.telephony.Annotation.ApnType;
-import android.telephony.CarrierConfigManager;
-import android.telephony.data.ApnSetting;
-import android.telephony.data.DataCallResponse;
-import android.text.TextUtils;
-import android.util.Pair;
-
-import com.android.internal.telephony.dataconnection.DataThrottler;
-import com.android.internal.telephony.util.TelephonyUtils;
-import com.android.telephony.Rlog;
-
-import java.util.ArrayList;
-import java.util.Random;
-
-/**
- * Retry manager allows a simple way to declare a series of
- * retry timeouts. After creating a RetryManager the configure
- * method is used to define the sequence. A simple linear series
- * may be initialized using configure with three integer parameters
- * The other configure method allows a series to be declared using
- * a string.
- *<p>
- * The format of the configuration string is the apn type followed by a series of parameters
- * separated by a comma. There are two name value pair parameters plus a series
- * of delay times. The units of of these delay times is unspecified.
- * The name value pairs which may be specified are:
- *<ul>
- *<li>max_retries=<value>
- *<li>default_randomizationTime=<value>
- *</ul>
- *<p>
- * apn type specifies the APN type that the retry pattern will apply for. "others" is for all other
- * APN types not specified in the config.
- *
- * max_retries is the number of times that incrementRetryCount
- * maybe called before isRetryNeeded will return false. if value
- * is infinite then isRetryNeeded will always return true.
- *
- * default_randomizationTime will be used as the randomizationTime
- * for delay times which have no supplied randomizationTime. If
- * default_randomizationTime is not defined it defaults to 0.
- *<p>
- * The other parameters define The series of delay times and each
- * may have an optional randomization value separated from the
- * delay time by a colon.
- *<p>
- * Examples:
- * <ul>
- * <li>3 retries for mms with no randomization value which means its 0:
- * <ul><li><code>"mms:1000, 2000, 3000"</code></ul>
- *
- * <li>10 retries for default APN with a 500 default randomization value for each and
- * the 4..10 retries all using 3000 as the delay:
- * <ul><li><code>"default:max_retries=10, default_randomization=500, 1000, 2000, 3000"</code></ul>
- *
- * <li>4 retries for supl APN with a 100 as the default randomization value for the first 2 values
- * and the other two having specified values of 500:
- * <ul><li><code>"supl:default_randomization=100, 1000, 2000, 4000:500, 5000:500"</code></ul>
- *
- * <li>Infinite number of retries for all other APNs with the first one at 1000, the second at 2000
- * all others will be at 3000.
- * <ul><li><code>"others:max_retries=infinite,1000,2000,3000</code></ul>
- * </ul>
- *
- * {@hide}
- */
-public class RetryManager {
-    public static final String LOG_TAG = "RetryManager";
-    public static final boolean DBG = true;
-    public static final boolean VDBG = false; // STOPSHIP if true
-
-    /**
-     * The default retry configuration for APNs. See above for the syntax.
-     */
-    private static final String DEFAULT_DATA_RETRY_CONFIG = "max_retries=3, 5000, 5000, 5000";
-
-    /**
-     * The APN type used for all other APNs retry configuration.
-     */
-    private static final String OTHERS_APN_TYPE = "others";
-
-    /**
-     * The default value (in milliseconds) for delay between APN trying (mInterApnDelay)
-     * within the same round
-     */
-    private static final long DEFAULT_INTER_APN_DELAY = 20000;
-
-    /**
-     * The default value (in milliseconds) for delay between APN trying (mFailFastInterApnDelay)
-     * within the same round when we are in fail fast mode
-     */
-    private static final long DEFAULT_INTER_APN_DELAY_FOR_PROVISIONING = 3000;
-
-    /**
-     * The default value (in milliseconds) for retrying APN after disconnect
-     */
-    private static final long DEFAULT_APN_RETRY_AFTER_DISCONNECT_DELAY = 10000;
-
-    /**
-     * The value indicating retry should not occur.
-     */
-    public static final long NO_RETRY = Long.MAX_VALUE;
-
-    /**
-     * The value indicating network did not suggest any retry delay
-     */
-    public static final long NO_SUGGESTED_RETRY_DELAY = DataCallResponse.RETRY_DURATION_UNDEFINED;
-
-    /**
-     * If the network suggests a retry delay in the data call setup response, we will retry
-     * the current APN setting again. The maximum retry count is to prevent that network
-     * keeps asking device to retry data setup forever and causes power consumption issue.
-     */
-    private static final int DEFAULT_MAX_SAME_APN_RETRY = 3;
-
-    /**
-     * The delay (in milliseconds) between APN trying within the same round
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private long mInterApnDelay;
-
-    /**
-     * The delay (in milliseconds) between APN trying within the same round when we are in
-     * fail fast mode
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private long mFailFastInterApnDelay;
-
-    /**
-     * The delay (in milliseconds) for APN retrying after disconnect (e.g. Modem suddenly reports
-     * data call lost)
-     */
-    private long mApnRetryAfterDisconnectDelay;
-
-    /**
-     * The counter for same APN retrying. See {@link #DEFAULT_MAX_SAME_APN_RETRY} for the details.
-     */
-    private int mSameApnRetryCount = 0;
-
-    /**
-     * The maximum times that frameworks retries data setup with the same APN. This value could be
-     * changed via carrier config. See {@link #DEFAULT_MAX_SAME_APN_RETRY} for the details.
-     */
-    private int mMaxSameApnRetry = DEFAULT_MAX_SAME_APN_RETRY;
-
-    /**
-     * Retry record with times in milli-seconds
-     */
-    private static class RetryRec {
-        long mDelayTime;
-        long mRandomizationTime;
-
-        RetryRec(long delayTime, long randomizationTime) {
-            mDelayTime = delayTime;
-            mRandomizationTime = randomizationTime;
-        }
-    }
-
-    /**
-     * The array of retry records
-     */
-    private ArrayList<RetryRec> mRetryArray = new ArrayList<RetryRec>();
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private Phone mPhone;
-
-    private final DataThrottler mDataThrottler;
-
-    /**
-     * Flag indicating whether retrying forever regardless the maximum retry count mMaxRetryCount
-     */
-    private boolean mRetryForever = false;
-
-    /**
-     * The maximum number of retries to attempt
-     */
-    private int mMaxRetryCount;
-
-    /**
-     * The current number of retries
-     */
-    private int mRetryCount = 0;
-
-    /**
-     * Random number generator. The random delay will be added into retry timer to avoid all devices
-     * around retrying the APN at the same time.
-     */
-    private Random mRng = new Random();
-
-    /**
-     * Retry manager configuration string. See top of the detailed explanation.
-     */
-    private String mConfig;
-
-    /**
-     * The list to store APN setting candidates for data call setup. Most of the carriers only have
-     * one APN, but few carriers have more than one.
-     */
-    private ArrayList<ApnSetting> mWaitingApns = new ArrayList<>();
-
-    /**
-     * Index pointing to the current trying APN from mWaitingApns
-     */
-    private int mCurrentApnIndex = -1;
-
-    /**
-     * Apn context type.
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private String mApnType;
-
-    private final @ApnType int apnType;
-
-    /**
-     * Retry manager constructor
-     * @param phone Phone object
-     * @param dataThrottler Data throttler
-     * @param apnType APN type
-     */
-    public RetryManager(@NonNull Phone phone, @NonNull DataThrottler dataThrottler,
-            @ApnType int apnType) {
-        mPhone = phone;
-        mDataThrottler = dataThrottler;
-        this.apnType = apnType;
-    }
-
-    /**
-     * Configure for using string which allow arbitrary
-     * sequences of times. See class comments for the
-     * string format.
-     *
-     * @return true if successful
-     */
-    @UnsupportedAppUsage
-    private boolean configure(String configStr) {
-        // Strip quotes if present.
-        if ((configStr.startsWith("\"") && configStr.endsWith("\""))) {
-            configStr = configStr.substring(1, configStr.length() - 1);
-        }
-
-        // Reset the retry manager since delay, max retry count, etc...will be reset.
-        reset();
-
-        if (DBG) log("configure: '" + configStr + "'");
-        mConfig = configStr;
-
-        if (!TextUtils.isEmpty(configStr)) {
-            long defaultRandomization = 0;
-
-            if (VDBG) log("configure: not empty");
-
-            String strArray[] = configStr.split(",");
-            for (int i = 0; i < strArray.length; i++) {
-                if (VDBG) log("configure: strArray[" + i + "]='" + strArray[i] + "'");
-                Pair<Boolean, Integer> value;
-                String splitStr[] = strArray[i].split("=", 2);
-                splitStr[0] = splitStr[0].trim();
-                if (VDBG) log("configure: splitStr[0]='" + splitStr[0] + "'");
-                if (splitStr.length > 1) {
-                    splitStr[1] = splitStr[1].trim();
-                    if (VDBG) log("configure: splitStr[1]='" + splitStr[1] + "'");
-                    if (TextUtils.equals(splitStr[0], "default_randomization")) {
-                        value = parseNonNegativeInt(splitStr[0], splitStr[1]);
-                        if (!value.first) return false;
-                        defaultRandomization = value.second;
-                    } else if (TextUtils.equals(splitStr[0], "max_retries")) {
-                        if (TextUtils.equals("infinite", splitStr[1])) {
-                            mRetryForever = true;
-                        } else {
-                            value = parseNonNegativeInt(splitStr[0], splitStr[1]);
-                            if (!value.first) return false;
-                            mMaxRetryCount = value.second;
-                        }
-                    } else {
-                        Rlog.e(LOG_TAG, "Unrecognized configuration name value pair: "
-                                        + strArray[i]);
-                        return false;
-                    }
-                } else {
-                    /**
-                     * Assume a retry time with an optional randomization value
-                     * following a ":"
-                     */
-                    splitStr = strArray[i].split(":", 2);
-                    splitStr[0] = splitStr[0].trim();
-                    RetryRec rr = new RetryRec(0, 0);
-                    value = parseNonNegativeInt("delayTime", splitStr[0]);
-                    if (!value.first) return false;
-                    rr.mDelayTime = value.second;
-
-                    // Check if optional randomization value present
-                    if (splitStr.length > 1) {
-                        splitStr[1] = splitStr[1].trim();
-                        if (VDBG) log("configure: splitStr[1]='" + splitStr[1] + "'");
-                        value = parseNonNegativeInt("randomizationTime", splitStr[1]);
-                        if (!value.first) return false;
-                        rr.mRandomizationTime = value.second;
-                    } else {
-                        rr.mRandomizationTime = defaultRandomization;
-                    }
-                    mRetryArray.add(rr);
-                }
-            }
-            if (mRetryArray.size() > mMaxRetryCount) {
-                mMaxRetryCount = mRetryArray.size();
-                if (VDBG) log("configure: setting mMaxRetryCount=" + mMaxRetryCount);
-            }
-        } else {
-            log("configure: cleared");
-        }
-
-        if (VDBG) log("configure: true");
-        return true;
-    }
-
-    /**
-     * Configure the retry manager
-     */
-    private void configureRetry() {
-        String configString = null;
-        String otherConfigString = null;
-
-        try {
-            if (TelephonyUtils.IS_DEBUGGABLE) {
-                // Using system properties is easier for testing from command line.
-                String config = SystemProperties.get("test.data_retry_config");
-                if (!TextUtils.isEmpty(config)) {
-                    configure(config);
-                    return;
-                }
-            }
-
-            CarrierConfigManager configManager = (CarrierConfigManager)
-                    mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
-            PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId());
-
-            mInterApnDelay = b.getLong(
-                    CarrierConfigManager.KEY_CARRIER_DATA_CALL_APN_DELAY_DEFAULT_LONG,
-                    DEFAULT_INTER_APN_DELAY);
-            mFailFastInterApnDelay = b.getLong(
-                    CarrierConfigManager.KEY_CARRIER_DATA_CALL_APN_DELAY_FASTER_LONG,
-                    DEFAULT_INTER_APN_DELAY_FOR_PROVISIONING);
-            mApnRetryAfterDisconnectDelay = b.getLong(
-                    CarrierConfigManager.KEY_CARRIER_DATA_CALL_APN_RETRY_AFTER_DISCONNECT_LONG,
-                    DEFAULT_APN_RETRY_AFTER_DISCONNECT_DELAY);
-            mMaxSameApnRetry = b.getInt(
-                    CarrierConfigManager
-                            .KEY_CARRIER_DATA_CALL_RETRY_NETWORK_REQUESTED_MAX_COUNT_INT,
-                    DEFAULT_MAX_SAME_APN_RETRY);
-
-            // Load all retry patterns for all different APNs.
-            String[] allConfigStrings = b.getStringArray(
-                    CarrierConfigManager.KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS);
-            if (allConfigStrings != null) {
-                for (String s : allConfigStrings) {
-                    if (!TextUtils.isEmpty(s)) {
-                        String splitStr[] = s.split(":", 2);
-                        if (splitStr.length == 2) {
-                            String apnTypeStr = splitStr[0].trim();
-                            // Check if this retry pattern is for the APN we want.
-                            if (apnTypeStr.equals(ApnSetting.getApnTypeString(apnType))) {
-                                // Extract the config string. Note that an empty string is valid
-                                // here, meaning no retry for the specified APN.
-                                configString = splitStr[1];
-                                break;
-                            } else if (apnTypeStr.equals(OTHERS_APN_TYPE)) {
-                                // Extract the config string. Note that an empty string is valid
-                                // here, meaning no retry for all other APNs.
-                                otherConfigString = splitStr[1];
-                            }
-                        }
-                    }
-                }
-            }
-
-            if (configString == null) {
-                if (otherConfigString != null) {
-                    configString = otherConfigString;
-                } else {
-                    // We should never reach here. If we reach here, it must be a configuration
-                    // error bug.
-                    log("Invalid APN retry configuration!. Use the default one now.");
-                    configString = DEFAULT_DATA_RETRY_CONFIG;
-                }
-            }
-        } catch (NullPointerException ex) {
-            // We should never reach here unless there is a bug
-            log("Failed to read configuration! Use the hardcoded default value.");
-
-            mInterApnDelay = DEFAULT_INTER_APN_DELAY;
-            mFailFastInterApnDelay = DEFAULT_INTER_APN_DELAY_FOR_PROVISIONING;
-            configString = DEFAULT_DATA_RETRY_CONFIG;
-        }
-
-        if (VDBG) {
-            log("mInterApnDelay = " + mInterApnDelay + ", mFailFastInterApnDelay = " +
-                    mFailFastInterApnDelay);
-        }
-
-        configure(configString);
-    }
-
-    /**
-     * Return the timer that should be used to trigger the data reconnection
-     */
-    @UnsupportedAppUsage
-    private long getRetryTimer() {
-        int index;
-        if (mRetryCount < mRetryArray.size()) {
-            index = mRetryCount;
-        } else {
-            index = mRetryArray.size() - 1;
-        }
-
-        long retVal;
-        if ((index >= 0) && (index < mRetryArray.size())) {
-            retVal = mRetryArray.get(index).mDelayTime + nextRandomizationTime(index);
-        } else {
-            retVal = 0;
-        }
-
-        if (DBG) log("getRetryTimer: " + retVal);
-        return retVal;
-    }
-
-    /**
-     * Parse an integer validating the value is not negative.
-     * @param name Name
-     * @param stringValue Value
-     * @return Pair.first == true if stringValue an integer >= 0
-     */
-    private Pair<Boolean, Integer> parseNonNegativeInt(String name, String stringValue) {
-        int value;
-        Pair<Boolean, Integer> retVal;
-        try {
-            value = Integer.parseInt(stringValue);
-            retVal = new Pair<>(validateNonNegativeInt(name, value), value);
-        } catch (NumberFormatException e) {
-            Rlog.e(LOG_TAG, name + " bad value: " + stringValue, e);
-            retVal = new Pair<>(false, 0);
-        }
-        if (VDBG) {
-            log("parseNonNegativeInt: " + name + ", " + stringValue + ", "
-                    + retVal.first + ", " + retVal.second);
-        }
-        return retVal;
-    }
-
-    /**
-     * Validate an integer is >= 0 and logs an error if not
-     * @param name Name
-     * @param value Value
-     * @return Pair.first
-     */
-    private boolean validateNonNegativeInt(String name, long value) {
-        boolean retVal;
-        if (value < 0) {
-            Rlog.e(LOG_TAG, name + " bad value: is < 0");
-            retVal = false;
-        } else {
-            retVal = true;
-        }
-        if (VDBG) log("validateNonNegative: " + name + ", " + value + ", " + retVal);
-        return retVal;
-    }
-
-    /**
-     * Return next random number for the index
-     * @param index Retry index
-     */
-    private long nextRandomizationTime(int index) {
-        long randomTime = mRetryArray.get(index).mRandomizationTime;
-        if (randomTime == 0) {
-            return 0;
-        } else {
-            return mRng.nextInt((int) randomTime);
-        }
-    }
-
-    private long getNetworkSuggestedRetryDelay() {
-        long retryElapseTime = mDataThrottler.getRetryTime(apnType);
-        if (retryElapseTime == NO_RETRY || retryElapseTime == NO_SUGGESTED_RETRY_DELAY) {
-            return retryElapseTime;
-        }
-
-        // The time from data throttler is system's elapsed time. We need to return the delta. If
-        // less than 0, then return 0 (i.e. retry immediately).
-        return Math.max(0, retryElapseTime - SystemClock.elapsedRealtime());
-    }
-
-    /**
-     * Get the next APN setting for data call setup.
-     * @return APN setting to try. {@code null} if cannot find any APN,
-     */
-    public @Nullable ApnSetting getNextApnSetting() {
-        if (mWaitingApns == null || mWaitingApns.size() == 0) {
-            log("Waiting APN list is null or empty.");
-            return null;
-        }
-
-        long networkSuggestedRetryDelay = getNetworkSuggestedRetryDelay();
-        if (networkSuggestedRetryDelay == NO_RETRY) {
-            log("Network suggested no retry.");
-            return null;
-        }
-
-        // If the network had suggested a retry delay, we should retry the current APN again
-        // (up to mMaxSameApnRetry times) instead of getting the next APN setting from
-        // our own list. If the APN waiting list has been reset before a setup data responses
-        // arrive (i.e. mCurrentApnIndex=-1), then ignore the network suggested retry.
-        if (mCurrentApnIndex != -1 && networkSuggestedRetryDelay != NO_SUGGESTED_RETRY_DELAY
-                && mSameApnRetryCount < mMaxSameApnRetry) {
-            mSameApnRetryCount++;
-            return mWaitingApns.get(mCurrentApnIndex);
-        }
-
-        mSameApnRetryCount = 0;
-
-        int index = mCurrentApnIndex;
-        // Loop through the APN list to find out the index of next non-permanent failed APN.
-        while (true) {
-            if (++index == mWaitingApns.size()) index = 0;
-
-            // Stop if we find the non-failed APN.
-            if (!mWaitingApns.get(index).getPermanentFailed()) {
-                break;
-            }
-
-            // If all APNs have permanently failed, bail out.
-            if (mWaitingApns.stream().allMatch(ApnSetting::getPermanentFailed)) {
-                return null;
-            }
-        }
-
-        mCurrentApnIndex = index;
-        return mWaitingApns.get(mCurrentApnIndex);
-    }
-
-    /**
-     * Get the delay for trying the next waiting APN from the list.
-     * @param failFastEnabled True if fail fast mode enabled. In this case we'll use a shorter
-     *                        delay.
-     * @return delay in milliseconds
-     */
-    public long getDelayForNextApn(boolean failFastEnabled) {
-
-        if (mWaitingApns == null || mWaitingApns.size() == 0) {
-            log("Waiting APN list is null or empty.");
-            return NO_RETRY;
-        }
-
-        long networkSuggestedDelay = getNetworkSuggestedRetryDelay();
-        log("Network suggested delay=" + networkSuggestedDelay + "ms");
-
-        if (networkSuggestedDelay == NO_RETRY) {
-            log("Network suggested not retrying.");
-            return NO_RETRY;
-        }
-
-        if (networkSuggestedDelay != NO_SUGGESTED_RETRY_DELAY
-                && mSameApnRetryCount < mMaxSameApnRetry) {
-            // If the network explicitly suggests a retry delay, we should use it, even in fail fast
-            // mode.
-            log("Network suggested retry in " + networkSuggestedDelay + " ms.");
-            return networkSuggestedDelay;
-        }
-
-        // In order to determine the delay to try next APN, we need to peek the next available APN.
-        // Case 1 - If we will start the next round of APN trying,
-        //    we use the exponential-growth delay. (e.g. 5s, 10s, 30s...etc.)
-        // Case 2 - If we are still within the same round of APN trying,
-        //    we use the fixed standard delay between APNs. (e.g. 20s)
-
-        int index = mCurrentApnIndex;
-        while (true) {
-            if (++index >= mWaitingApns.size()) index = 0;
-
-            // Stop if we find the non-failed APN.
-            if (!mWaitingApns.get(index).getPermanentFailed()) {
-                break;
-            }
-
-            // If all APNs have permanently failed, bail out.
-            if (mWaitingApns.stream().allMatch(ApnSetting::getPermanentFailed)) {
-                log("All APNs have permanently failed.");
-                return NO_RETRY;
-            }
-        }
-
-        long delay;
-        if (index <= mCurrentApnIndex) {
-            // Case 1, if the next APN is in the next round.
-            if (!mRetryForever && mRetryCount + 1 > mMaxRetryCount) {
-                log("Reached maximum retry count " + mMaxRetryCount + ".");
-                return NO_RETRY;
-            }
-            delay = getRetryTimer();
-            ++mRetryCount;
-        } else {
-            // Case 2, if the next APN is still in the same round.
-            delay = mInterApnDelay;
-        }
-
-        if (failFastEnabled && delay > mFailFastInterApnDelay) {
-            // If we enable fail fast mode, and the delay we got is longer than
-            // fail-fast delay (mFailFastInterApnDelay), use the fail-fast delay.
-            // If the delay we calculated is already shorter than fail-fast delay,
-            // then ignore fail-fast delay.
-            delay = mFailFastInterApnDelay;
-        }
-
-        return delay;
-    }
-
-    /**
-     * Mark the APN setting permanently failed.
-     * @param apn APN setting to be marked as permanently failed
-     * */
-    public void markApnPermanentFailed(ApnSetting apn) {
-        if (apn != null) {
-            apn.setPermanentFailed(true);
-        }
-    }
-
-    /**
-     * Reset the retry manager.
-     */
-    private void reset() {
-        mMaxRetryCount = 0;
-        mRetryCount = 0;
-        mCurrentApnIndex = -1;
-        mSameApnRetryCount = 0;
-        mRetryArray.clear();
-    }
-
-    /**
-     * Set waiting APNs for retrying in case needed.
-     * @param waitingApns Waiting APN list
-     */
-    public void setWaitingApns(ArrayList<ApnSetting> waitingApns) {
-
-        if (waitingApns == null) {
-            log("No waiting APNs provided");
-            return;
-        }
-
-        mWaitingApns = waitingApns;
-
-        // Since we replace the entire waiting APN list, we need to re-config this retry manager.
-        configureRetry();
-
-        for (ApnSetting apn : mWaitingApns) {
-            apn.setPermanentFailed(false);
-        }
-
-        log("Setting " + mWaitingApns.size() + " waiting APNs.");
-
-        if (VDBG) {
-            for (int i = 0; i < mWaitingApns.size(); i++) {
-                log("  [" + i + "]:" + mWaitingApns.get(i));
-            }
-        }
-    }
-
-    /**
-     * Get the list of waiting APNs.
-     * @return the list of waiting APNs
-     */
-    public @NonNull ArrayList<ApnSetting> getWaitingApns() {
-        return mWaitingApns;
-    }
-
-    /**
-     * Get the delay in milliseconds for APN retry after disconnect
-     * @return The delay in milliseconds
-     */
-    public long getRetryAfterDisconnectDelay() {
-        return mApnRetryAfterDisconnectDelay;
-    }
-
-    public String toString() {
-        if (mConfig == null) return "";
-        return "RetryManager: apnType=" + ApnSetting.getApnTypeString(apnType)
-                + " mRetryCount="
-                + mRetryCount + " mMaxRetryCount=" + mMaxRetryCount + " mCurrentApnIndex="
-                + mCurrentApnIndex + " mSameApnRtryCount=" + mSameApnRetryCount
-                + " networkSuggestedDelay=" + getNetworkSuggestedRetryDelay() + " mRetryForever="
-                + mRetryForever + " mInterApnDelay=" + mInterApnDelay
-                + " mApnRetryAfterDisconnectDelay=" + mApnRetryAfterDisconnectDelay
-                + " mConfig={" + mConfig + "}";
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private void log(String s) {
-        Rlog.d(LOG_TAG, "[" + ApnSetting.getApnTypeString(apnType) + "] " + s);
-    }
-}
diff --git a/src/java/com/android/internal/telephony/SMSDispatcher.java b/src/java/com/android/internal/telephony/SMSDispatcher.java
index 5bc9d17..a78242a 100644
--- a/src/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/SMSDispatcher.java
@@ -27,16 +27,22 @@
 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;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 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;
@@ -60,6 +66,7 @@
 import android.telephony.PhoneNumberUtils;
 import android.telephony.ServiceState;
 import android.telephony.SmsManager;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.Html;
 import android.text.Spanned;
@@ -80,6 +87,10 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
 import com.android.internal.telephony.cdma.sms.UserData;
+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;
@@ -139,11 +150,30 @@
     /** 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;
     protected static final int EVENT_GET_IMS_SERVICE = 16;
 
+    /** Last TP - Message Reference value update to SIM */
+    private static final int EVENT_TPMR_SIM_UPDATE_RESPONSE = 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
@@ -155,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;
@@ -190,6 +229,9 @@
     @VisibleForTesting
     public int mCarrierMessagingTimeout = 10 * 60 * 1000; //10 minutes
 
+    /** Used for storing last TP - Message Reference used*/
+    private int mMessageRef = -1;
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     protected static int getNextConcatenatedRef() {
         sConcatenatedRef += 1;
@@ -215,10 +257,33 @@
                 com.android.internal.R.bool.config_sms_capable);
         mSmsSendDisabled = !mTelephonyManager.getSmsSendCapableForPhone(
                 mPhone.getPhoneId(), mSmsCapable);
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_SIM_STATE_CHANGED);
+        mContext.registerReceiver(mBroadcastReceiver, intentFilter);
         Rlog.d(TAG, "SMSDispatcher: ctor mSmsCapable=" + mSmsCapable + " format=" + getFormat()
                 + " mSmsSendDisabled=" + mSmsSendDisabled);
     }
 
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(final Context context, Intent intent) {
+            Rlog.d(TAG, "Received broadcast " + intent.getAction());
+            if (Intent.ACTION_SIM_STATE_CHANGED.equals(intent.getAction())) {
+                if (!intent.hasExtra(Intent.EXTRA_SIM_STATE)) {
+                    Rlog.d(TAG, "Extra not found in intent.");
+                } else {
+                    String simState = intent.getStringExtra(Intent.EXTRA_SIM_STATE);
+                    if (simState.equals(Intent.SIM_STATE_LOADED)) {
+                        Rlog.d(TAG, "SIM_STATE_CHANGED : SIM_LOADED");
+                        Message msg = obtainMessage(EVENT_SIM_LOADED);
+                        msg.arg1 = getSubId();
+                        sendMessage(msg);
+                    }
+                }
+            }
+        }
+    };
+
     /**
      * Observe the secure setting for updated premium sms determination rules
      */
@@ -262,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.
      *
@@ -280,83 +365,181 @@
     @Override
     public void handleMessage(Message msg) {
         switch (msg.what) {
-        case EVENT_SEND_SMS_COMPLETE:
-            // An outbound SMS has been successfully transferred, or failed.
-            handleSendComplete((AsyncResult) msg.obj);
-            break;
+            case EVENT_SEND_SMS_COMPLETE:
+                // An outbound SMS has been successfully transferred, or failed.
+                handleSendComplete((AsyncResult) msg.obj);
+                break;
 
-        case EVENT_SEND_RETRY:
-            Rlog.d(TAG, "SMS retry..");
-            sendRetrySms((SmsTracker) msg.obj);
-            break;
+            case EVENT_SEND_RETRY:
+                Rlog.d(TAG, "SMS retry..");
+                sendRetrySms((SmsTracker) msg.obj);
+                break;
 
-        case EVENT_SEND_LIMIT_REACHED_CONFIRMATION:
-            handleReachSentLimit((SmsTracker[]) (msg.obj));
-            break;
+            case EVENT_SEND_LIMIT_REACHED_CONFIRMATION:
+                handleReachSentLimit((SmsTracker[]) (msg.obj));
+                break;
 
-        case EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE:
-            handleConfirmShortCode(false, (SmsTracker[]) (msg.obj));
-            break;
+            case EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE:
+                handleConfirmShortCode(false, (SmsTracker[]) (msg.obj));
+                break;
 
-        case EVENT_CONFIRM_SEND_TO_PREMIUM_SHORT_CODE:
-            handleConfirmShortCode(true, (SmsTracker[]) (msg.obj));
-            break;
+            case EVENT_CONFIRM_SEND_TO_PREMIUM_SHORT_CODE:
+                handleConfirmShortCode(true, (SmsTracker[]) (msg.obj));
+                break;
 
-        case EVENT_SEND_CONFIRMED_SMS:
-        {
-            SmsTracker[] trackers = (SmsTracker[]) msg.obj;
-            for (SmsTracker tracker : trackers) {
-                sendSms(tracker);
-            }
-            mPendingTrackerCount--;
-            break;
-        }
-
-        case EVENT_SENDING_NOT_ALLOWED:
-        {
-            SmsTracker[] trackers = (SmsTracker[]) msg.obj;
-            Rlog.d(TAG, "SMSDispatcher: EVENT_SENDING_NOT_ALLOWED - "
-                    + "sending SHORT_CODE_NEVER_ALLOWED error code.");
-            handleSmsTrackersFailure(
-                    trackers, SmsManager.RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED, NO_ERROR_CODE);
-            break;
-        }
-
-        case EVENT_STOP_SENDING:
-        {
-            SmsTracker[] trackers = (SmsTracker[]) msg.obj;
-            int error;
-            if (msg.arg1 == ConfirmDialogListener.SHORT_CODE_MSG) {
-                if (msg.arg2 == ConfirmDialogListener.NEVER_ALLOW) {
-                    error = SmsManager.RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED;
-                    Rlog.d(TAG, "SMSDispatcher: EVENT_STOP_SENDING - "
-                            + "sending SHORT_CODE_NEVER_ALLOWED error code.");
-                } else {
-                    error = SmsManager.RESULT_ERROR_SHORT_CODE_NOT_ALLOWED;
-                    Rlog.d(TAG, "SMSDispatcher: EVENT_STOP_SENDING - "
-                            + "sending SHORT_CODE_NOT_ALLOWED error code.");
+            case EVENT_SEND_CONFIRMED_SMS: {
+                SmsTracker[] trackers = (SmsTracker[]) msg.obj;
+                for (SmsTracker tracker : trackers) {
+                    sendSms(tracker);
                 }
-            } else if (msg.arg1 == ConfirmDialogListener.RATE_LIMIT) {
-                error = SmsManager.RESULT_ERROR_LIMIT_EXCEEDED;
-                Rlog.d(TAG, "SMSDispatcher: EVENT_STOP_SENDING - "
-                        + "sending LIMIT_EXCEEDED error code.");
-            } else {
-                error = SmsManager.RESULT_UNEXPECTED_EVENT_STOP_SENDING;
-                Rlog.e(TAG, "SMSDispatcher: EVENT_STOP_SENDING - unexpected cases.");
+                mPendingTrackerCount--;
+                break;
             }
 
-            handleSmsTrackersFailure(trackers, error, NO_ERROR_CODE);
-            mPendingTrackerCount--;
-            break;
+            case EVENT_SENDING_NOT_ALLOWED: {
+                SmsTracker[] trackers = (SmsTracker[]) msg.obj;
+                Rlog.d(TAG, "SMSDispatcher: EVENT_SENDING_NOT_ALLOWED - "
+                        + "sending SHORT_CODE_NEVER_ALLOWED error code.");
+                handleSmsTrackersFailure(
+                        trackers, SmsManager.RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED, NO_ERROR_CODE);
+                break;
+            }
+
+            case EVENT_STOP_SENDING: {
+                SmsTracker[] trackers = (SmsTracker[]) msg.obj;
+                int error;
+                if (msg.arg1 == ConfirmDialogListener.SHORT_CODE_MSG) {
+                    if (msg.arg2 == ConfirmDialogListener.NEVER_ALLOW) {
+                        error = SmsManager.RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED;
+                        Rlog.d(TAG, "SMSDispatcher: EVENT_STOP_SENDING - "
+                                + "sending SHORT_CODE_NEVER_ALLOWED error code.");
+                    } else {
+                        error = SmsManager.RESULT_ERROR_SHORT_CODE_NOT_ALLOWED;
+                        Rlog.d(TAG, "SMSDispatcher: EVENT_STOP_SENDING - "
+                                + "sending SHORT_CODE_NOT_ALLOWED error code.");
+                    }
+                } else if (msg.arg1 == ConfirmDialogListener.RATE_LIMIT) {
+                    error = SmsManager.RESULT_ERROR_LIMIT_EXCEEDED;
+                    Rlog.d(TAG, "SMSDispatcher: EVENT_STOP_SENDING - "
+                            + "sending LIMIT_EXCEEDED error code.");
+                } else {
+                    error = SmsManager.RESULT_UNEXPECTED_EVENT_STOP_SENDING;
+                    Rlog.e(TAG, "SMSDispatcher: EVENT_STOP_SENDING - unexpected cases.");
+                }
+
+                handleSmsTrackersFailure(trackers, error, NO_ERROR_CODE);
+                mPendingTrackerCount--;
+                break;
+            }
+
+            case EVENT_NEW_SMS_STATUS_REPORT:
+                handleStatusReport(msg.obj);
+                break;
+            case EVENT_TPMR_SIM_UPDATE_RESPONSE:
+                handleMessageRefStatus(msg);
+                break;
+
+            case EVENT_SIM_LOADED:
+                /* sim TPMR value is given higher priority if both are non-negative number.
+                   Use case:
+                   if sim was used on another device and inserted in a new device,
+                   that device will start sending the next TPMR after reading from the SIM.
+                 */
+                mMessageRef = getTpmrValueFromSIM();
+                if (mMessageRef == -1) {
+                    SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
+                            .getSubscriptionInfoInternal(msg.arg1);
+                    if (subInfo != null) {
+                        mMessageRef = subInfo.getLastUsedTPMessageReference();
+                    }
+                }
+                break;
+
+            default:
+                Rlog.e(TAG, "handleMessage() ignoring message of unexpected type " + msg.what);
+        }
+    }
+
+    private void handleMessageRefStatus(Message msg) {
+        AsyncResult ar = (AsyncResult) msg.obj;
+        if (ar.exception != null) {
+            Rlog.e(TAG, "Failed to update TP - Message reference value to SIM " + ar.exception);
+        } else {
+            Rlog.d(TAG, "TP - Message reference updated to SIM Successfully");
+        }
+    }
+
+    private void updateTPMessageReference() {
+        updateSIMLastTPMRValue(mMessageRef);
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            SubscriptionManagerService.getInstance()
+                    .setLastUsedTPMessageReference(getSubId(), mMessageRef);
+        } catch (SecurityException e) {
+            Rlog.e(TAG, "Security Exception caused on messageRef updation to DB " + e.getMessage());
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    private void updateSIMLastTPMRValue(int messageRef) {
+        Message msg = obtainMessage(EVENT_TPMR_SIM_UPDATE_RESPONSE);
+        IccRecords iccRecords = getIccRecords();
+        if (iccRecords != null) {
+            iccRecords.setSmssTpmrValue(messageRef, msg);
+        }
+    }
+
+    private int getTpmrValueFromSIM() {
+        IccRecords iccRecords = getIccRecords();
+        if (iccRecords != null) {
+            return iccRecords.getSmssTpmrValue();
+        }
+        return -1;
+    }
+
+    private IccRecords getIccRecords() {
+        if (mPhone != null && mPhone.getIccRecords() != null) {
+            return mPhone.getIccRecords();
+        }
+        return null;
+    }
+
+    /**
+     * 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
+     */
+    public int nextMessageRef() {
+        if (!isMessageRefIncrementViaTelephony()) {
+            return 0;
         }
 
-        case EVENT_NEW_SMS_STATUS_REPORT:
-            handleStatusReport(msg.obj);
-            break;
+        mMessageRef = (mMessageRef + 1) % 256;
+        updateTPMessageReference();
+        return mMessageRef;
+    }
 
-        default:
-            Rlog.e(TAG, "handleMessage() ignoring message of unexpected type " + msg.what);
+    /**
+     * 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;
     }
 
     /**
@@ -569,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
@@ -663,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);
         }
 
@@ -712,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) {
@@ -919,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()
@@ -942,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
@@ -954,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()),
@@ -981,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;
@@ -1032,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));
@@ -1133,14 +1327,15 @@
     @UnsupportedAppUsage
     protected void sendData(String callingPackage, String destAddr, String scAddr, int destPort,
             byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent, boolean isForVvm) {
+        int messageRef = nextMessageRef();
         SmsMessageBase.SubmitPduBase pdu = getSubmitPdu(
-                scAddr, destAddr, destPort, data, (deliveryIntent != null));
+                scAddr, destAddr, destPort, data, (deliveryIntent != null), messageRef);
         if (pdu != null) {
             HashMap map = getSmsTrackerMap(destAddr, scAddr, destPort, data, pdu);
             SmsTracker tracker = getSmsTracker(callingPackage, map, sentIntent, deliveryIntent,
                     getFormat(), null /*messageUri*/, false /*expectMore*/,
                     null /*fullMessageText*/, false /*isText*/,
-                    true /*persistMessage*/, isForVvm, 0L /* messageId */);
+                    true /*persistMessage*/, isForVvm, 0L /* messageId */, messageRef);
 
             if (!sendSmsByCarrierApp(true /* isDataSms */, tracker)) {
                 sendSubmitPdu(tracker);
@@ -1252,18 +1447,131 @@
      *                 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(
-                scAddr, destAddr, text, (deliveryIntent != null), null, priority, validityPeriod);
+                scAddr, destAddr, text, (deliveryIntent != null), null, priority, validityPeriod,
+                messageRef);
         if (pdu != null) {
             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);
+                    persistMessage, priority, validityPeriod, isForVvm, messageId, messageRef,
+                    skipShortCodeCheck);
 
             if (!sendSmsByCarrierApp(false /* isDataSms */, tracker)) {
                 sendSubmitPdu(tracker);
@@ -1319,6 +1627,14 @@
     protected abstract SmsMessageBase.SubmitPduBase getSubmitPdu(String scAddr, String destAddr,
             int destPort, byte[] message, boolean statusReportRequested);
 
+    protected abstract SmsMessageBase.SubmitPduBase getSubmitPdu(String scAddr, String destAddr,
+            String message, boolean statusReportRequested, SmsHeader smsHeader,
+            int priority, int validityPeriod, int messageRef);
+
+
+    protected abstract SmsMessageBase.SubmitPduBase getSubmitPdu(String scAddr, String destAddr,
+            int destPort, byte[] message, boolean statusReportRequested, int messageRef);
+
     /**
      * Calculate the number of septets needed to encode the message. This function should only be
      * called for individual segments of multipart message.
@@ -1494,12 +1810,13 @@
             if (deliveryIntents != null && deliveryIntents.size() > i) {
                 deliveryIntent = deliveryIntents.get(i);
             }
-
+            int messageRef = nextMessageRef();
             trackers[i] =
                 getNewSubmitPduTracker(callingPkg, destAddr, scAddr, parts.get(i), smsHeader,
                         encoding, sentIntent, deliveryIntent, (i == (msgCount - 1)),
                         unsentPartCount, anyPartFailed, messageUri,
-                        fullMessageText, priority, expectMore, validityPeriod, messageId);
+                        fullMessageText, priority, expectMore, validityPeriod, messageId,
+                        messageRef);
             if (trackers[i] == null) {
                 triggerSentIntentForFailure(sentIntents);
                 return;
@@ -1509,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)));
@@ -1537,7 +1852,7 @@
             PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart,
             AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri,
             String fullMessageText, int priority, boolean expectMore, int validityPeriod,
-            long messageId) {
+            long messageId, int messageRef) {
         if (isCdmaMo()) {
             UserData uData = new UserData();
             uData.payloadStr = message;
@@ -1567,7 +1882,7 @@
                         getFormat(), unsentPartCount, anyPartFailed, messageUri, smsHeader,
                         (!lastPart || expectMore), fullMessageText, true /*isText*/,
                         true /*persistMessage*/, priority, validityPeriod, false /* isForVvm */,
-                        messageId);
+                        messageId, messageRef, false);
             } else {
                 Rlog.e(TAG, "CdmaSMSDispatcher.getNewSubmitPduTracker(): getSubmitPdu() returned "
                         + "null " + SmsController.formatCrossStackMessageId(messageId));
@@ -1578,15 +1893,14 @@
                     com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress,
                             destinationAddress, message, deliveryIntent != null,
                             SmsHeader.toByteArray(smsHeader), encoding, smsHeader.languageTable,
-                            smsHeader.languageShiftTable, validityPeriod);
+                            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);
+                        messageId, messageRef, false);
             } else {
                 Rlog.e(TAG, "GsmSMSDispatcher.getNewSubmitPduTracker(): getSubmitPdu() returned "
                         + "null " + SmsController.formatCrossStackMessageId(messageId));
@@ -1744,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();
@@ -1785,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) {
@@ -2082,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");
@@ -2091,7 +2413,8 @@
                 AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri,
                 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 validityPeriod, boolean isForVvm, long messageId, int carrierId,
+                int messageRef, boolean skipShortCodeDestAddrCheck) {
             mData = data;
             mSentIntent = sentIntent;
             mDeliveryIntent = deliveryIntent;
@@ -2102,7 +2425,7 @@
             mExpectMore = expectMore;
             mImsRetry = 0;
             mUsesImsServiceForIms = false;
-            mMessageRef = 0;
+            mMessageRef = messageRef;
             mUnsentPartCount = unsentPartCount;
             mAnyPartFailed = anyPartFailed;
             mMessageUri = messageUri;
@@ -2117,6 +2440,7 @@
             mIsForVvm = isForVvm;
             mMessageId = messageId;
             mCarrierId = carrierId;
+            mSkipShortCodeDestAddrCheck = skipShortCodeDestAddrCheck;
         }
 
         public HashMap<String, Object> getData() {
@@ -2131,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;
         }
@@ -2384,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) {
+            long messageId, int messageRef, boolean skipShortCodeCheck) {
         // Get package info via packagemanager
         UserHandle callingUser = UserHandle.getUserHandleForUid(Binder.getCallingUid());
         final int userId = callingUser.getIdentifier();
@@ -2401,28 +2734,30 @@
         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());
+                validityPeriod, isForVvm, messageId, mPhone.getCarrierId(), messageRef,
+                skipShortCodeCheck);
     }
 
     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,
-            boolean isForVvm, long messageId) {
+            boolean isForVvm, long messageId, int messageRef) {
         return getSmsTracker(callingPackage, data, sentIntent, deliveryIntent, format,
                 null/*unsentPartCount*/, null/*anyPartFailed*/, messageUri, null/*smsHeader*/,
                 expectMore, fullMessageText, isText, persistMessage,
                 SMS_MESSAGE_PRIORITY_NOT_SPECIFIED, SMS_MESSAGE_PERIOD_NOT_SPECIFIED, isForVvm,
-                messageId);
+                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 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);
+                isForVvm, messageId, messageRef, skipShortCodeCheck);
     }
 
     protected HashMap<String, Object> getSmsTrackerMap(String destAddr, String scAddr,
@@ -2591,7 +2926,7 @@
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     protected int getSubId() {
-        return SubscriptionController.getInstance().getSubIdUsingPhoneId(mPhone.getPhoneId());
+        return SubscriptionManager.getSubscriptionId(mPhone.getPhoneId());
     }
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -2641,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 751d59a..50eea7f
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -37,7 +37,6 @@
 import android.content.SharedPreferences;
 import android.content.res.Resources;
 import android.hardware.radio.V1_0.CellInfoType;
-import android.net.NetworkCapabilities;
 import android.os.AsyncResult;
 import android.os.BaseBundle;
 import android.os.Build;
@@ -72,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;
@@ -93,11 +91,14 @@
 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.dataconnection.DataConnection;
+import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.metrics.ServiceStateStats;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
+import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState;
 import com.android.internal.telephony.uicc.IccCardStatus.CardState;
 import com.android.internal.telephony.uicc.IccRecords;
@@ -146,7 +147,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private UiccController mUiccController = null;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private UiccCardApplication mUiccApplcation = null;
+    private UiccCardApplication mUiccApplication = null;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private IccRecords mIccRecords = null;
 
@@ -182,13 +183,12 @@
 
     private final Set<Integer> mRadioPowerOffReasons = new HashSet();
 
-    // TODO - this should not be public, right now used externally GsmConnetion.
+    // TODO - this should not be public, right now used externally GsmConnection.
     public RestrictedState mRestrictedState;
 
     /**
-     * A unique identifier to track requests associated with a poll
-     * and ignore stale responses.  The value is a count-down of
-     * expected responses in this pollingContext.
+     * A unique identifier to track requests associated with a poll and ignore stale responses.
+     * The value is a count-down of expected responses in this pollingContext.
      */
     @VisibleForTesting
     public int[] mPollingContext;
@@ -217,13 +217,12 @@
     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();
 
-    /* Radio power off pending flag and tag counter */
-    private boolean mPendingRadioPowerOffAfterDataOff = false;
-    private int mPendingRadioPowerOffAfterDataOffTag = 0;
+    /* Radio power off pending flag */
+    // @GuardedBy("this")
+    private volatile boolean mPendingRadioPowerOffAfterDataOff = false;
 
     /** Waiting period before recheck gprs and voice registration. */
     public static final int DEFAULT_GPRS_CHECK_PERIOD_MILLIS = 60 * 1000;
@@ -280,12 +279,10 @@
     protected static final int EVENT_RADIO_POWER_OFF_DONE              = 54;
     protected static final int EVENT_PHYSICAL_CHANNEL_CONFIG           = 55;
     protected static final int EVENT_CELL_LOCATION_RESPONSE            = 56;
-    protected static final int EVENT_CARRIER_CONFIG_CHANGED            = 57;
     private static final int EVENT_POLL_STATE_REQUEST                  = 58;
     // Timeout event used when delaying radio power off to wait for IMS deregistration to happen.
     private static final int EVENT_POWER_OFF_RADIO_IMS_DEREG_TIMEOUT   = 62;
     protected static final int EVENT_RESET_LAST_KNOWN_CELL_IDENTITY    = 63;
-    private static final int EVENT_REGISTER_DATA_NETWORK_EXISTING_CHANGED = 64;
     // Telecom has un/registered a PhoneAccount that provides OTT voice calling capability, e.g.
     // wi-fi calling.
     protected static final int EVENT_TELECOM_VOICE_SERVICE_STATE_OVERRIDE_CHANGED = 65;
@@ -312,7 +309,7 @@
     // Show PLMN only and only if this bit is set.
     public static final int CARRIER_NAME_DISPLAY_BITMASK_SHOW_PLMN = 1 << 1;
 
-    private List<Message> mPendingCellInfoRequests = new LinkedList<Message>();
+    private List<Message> mPendingCellInfoRequests = new LinkedList<>();
     // @GuardedBy("mPendingCellInfoRequests")
     private boolean mIsPendingCellInfoRequest = false;
 
@@ -323,14 +320,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;
@@ -349,13 +342,11 @@
 
     @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 =
         new SstSubscriptionsChangedListener();
 
-
     private final RatRatcheter mRatRatcheter;
 
     private final LocaleTracker mLocaleTracker;
@@ -368,6 +359,7 @@
     private final LocalLog mCdnrLogs = new LocalLog(64);
 
     private Pattern mOperatorNameStringPattern;
+    private PersistableBundle mCarrierConfig;
 
     private class SstSubscriptionsChangedListener extends OnSubscriptionsChangedListener {
 
@@ -387,7 +379,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
@@ -396,72 +387,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,15 +543,13 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             final String action = intent.getAction();
-            if (action.equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
-                int phoneId = intent.getExtras().getInt(CarrierConfigManager.EXTRA_SLOT_INDEX);
-                // Ignore the carrier config changed if the phoneId is not matched.
-                if (phoneId == mPhone.getPhoneId()) {
-                    sendEmptyMessage(EVENT_CARRIER_CONFIG_CHANGED);
-                }
-            } else if (action.equals(Intent.ACTION_LOCALE_CHANGED)) {
+            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);
@@ -579,6 +560,10 @@
         }
     };
 
+    private final CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener =
+            (slotIndex, subId, carrierId, specificCarrierId) ->
+                    onCarrierConfigurationChanged(slotIndex);
+
     //CDMA
     // Min values used to by getOtasp()
     public static final String UNACTIVATED_MIN2_VALUE = "000000";
@@ -620,11 +605,17 @@
     private int mLastKnownAreaCode = CellInfo.UNAVAILABLE;
 
     /**
-     * Indicating if there is any data network existing. This is used in airplane mode turning on
-     * scenario, where service state tracker should wait all data disconnected before powering
-     * down the modem.
+     * Data network controller callback for all data disconnected. This is used when turning on
+     * airplane mode, where service state tracker should wait for all data disconnected on all
+     * subscriptions before powering down the modem.
      */
-    private boolean mAnyDataExisting = false;
+    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()
@@ -655,15 +646,20 @@
         mCi.registerForCellInfoList(this, EVENT_UNSOL_CELL_INFO_LIST, null);
         mCi.registerForPhysicalChannelConfiguration(this, EVENT_PHYSICAL_CHANNEL_CONFIG, null);
 
-        mSubscriptionController = SubscriptionController.getInstance();
+        mSubscriptionManagerService = SubscriptionManagerService.getInstance();
         mSubscriptionManager = SubscriptionManager.from(phone.getContext());
         mSubscriptionManager.addOnSubscriptionsChangedListener(
                 new android.os.HandlerExecutor(this), mOnSubscriptionsChangedListener);
         mRestrictedState = new RestrictedState();
 
+        mCarrierConfig = getCarrierConfig();
+        CarrierConfigManager ccm = mPhone.getContext().getSystemService(CarrierConfigManager.class);
+        // Callback which directly handle config change should be executed in handler thread
+        ccm.registerCarrierConfigChangeListener(this::post, mCarrierConfigChangeListener);
+
         mAccessNetworksManager = mPhone.getAccessNetworksManager();
         mOutOfServiceSS = new ServiceState();
-        mOutOfServiceSS.setOutOfService(mAccessNetworksManager.isInLegacyMode(), false);
+        mOutOfServiceSS.setOutOfService(false);
 
         for (int transportType : mAccessNetworksManager.getAvailableTransports()) {
             mRegStateManagers.append(transportType, new NetworkRegistrationManager(
@@ -686,12 +682,11 @@
                 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);
 
-
         mPhone.getCarrierActionAgent().registerForCarrierAction(CARRIER_ACTION_SET_RADIO_ENABLED,
                 this, EVENT_RADIO_POWER_FROM_CARRIER, null, false);
 
@@ -699,7 +694,6 @@
         Context context = mPhone.getContext();
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_LOCALE_CHANGED);
-        filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
         filter.addAction(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED);
         context.registerReceiver(mIntentReceiver, filter);
 
@@ -721,8 +715,31 @@
         registerForImsCapabilityChanged(mCSST,
                 CarrierServiceStateTracker.CARRIER_EVENT_IMS_CAPABILITIES_CHANGED, null);
 
-        if (mPhone.isUsingNewDataStack()) {
-            sendEmptyMessage(EVENT_REGISTER_DATA_NETWORK_EXISTING_CHANGED);
+        mDataDisconnectedCallback = new DataNetworkControllerCallback(this::post) {
+            @Override
+            public void onAnyDataNetworkExistingChanged(boolean anyDataExisting) {
+                log("onAnyDataNetworkExistingChanged: anyDataExisting=" + anyDataExisting);
+                if (!anyDataExisting) {
+                    sendEmptyMessage(EVENT_ALL_DATA_DISCONNECTED);
+                }
+            }
+        };
+
+        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);
         }
     }
 
@@ -759,9 +776,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;
@@ -854,17 +871,28 @@
         mPhone.getCarrierActionAgent().unregisterForCarrierAction(this,
                 CARRIER_ACTION_SET_RADIO_ENABLED);
         mPhone.getContext().unregisterReceiver(mIntentReceiver);
+        CarrierConfigManager ccm = mPhone.getContext().getSystemService(CarrierConfigManager.class);
+        if (ccm != null && mCarrierConfigChangeListener != null) {
+            ccm.unregisterCarrierConfigChangeListener(mCarrierConfigChangeListener);
+        }
         if (mCSST != null) {
             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;
@@ -898,7 +926,7 @@
         if (nrs != null) {
             int rat = ServiceState.networkTypeToRilRadioTechnology(
                     nrs.getAccessNetworkTechnology());
-            int drs = regCodeToServiceState(nrs.getRegistrationState());
+            int drs = regCodeToServiceState(nrs.getNetworkRegistrationState());
             return new Pair<>(drs, rat);
         }
         return null;
@@ -1054,7 +1082,7 @@
      * @return the current reasons for which the radio is off.
      */
     public Set<Integer> getRadioPowerOffReasons() {
-        return mRadioPowerOffReasons;
+        return Set.copyOf(mRadioPowerOffReasons);
     }
 
     /**
@@ -1080,14 +1108,14 @@
     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);
     }
 
     /**
      * Turn on or off radio power with option to specify whether it's for emergency call and specify
      * a reason for setting the power state.
-     * More details check {@link PhoneInternalInterface#setRadioPower(
-     * boolean, boolean, boolean, boolean, int)}.
+     * More details check {@link
+     * PhoneInternalInterface#setRadioPowerForReason(boolean, boolean, boolean, boolean, int)}.
      */
     public void setRadioPowerForReason(boolean power, boolean forEmergencyCall,
             boolean isSelectedPhoneForEmergencyCall, boolean forceApply, int reason) {
@@ -1110,7 +1138,7 @@
         if (power && !mRadioPowerOffReasons.isEmpty()) {
             log("setRadioPowerForReason " + "power: " + power + " forEmergencyCall= "
                     + forEmergencyCall + " isSelectedPhoneForEmergencyCall: "
-                    + isSelectedPhoneForEmergencyCall + " forceApply " + forceApply + "reason:"
+                    + isSelectedPhoneForEmergencyCall + " forceApply " + forceApply + " reason: "
                     + reason + " will not power on the radio as it is powered off for the "
                     + "following reasons: " + mRadioPowerOffReasons + ".");
             return;
@@ -1121,23 +1149,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
@@ -1189,22 +1200,9 @@
         switch (msg.what) {
             case EVENT_SET_RADIO_POWER_OFF:
                 synchronized(this) {
-                    if (mPhone.isUsingNewDataStack()) {
-                        mPendingRadioPowerOffAfterDataOff = false;
-                        log("Wait for all data networks torn down timed out. Power off now.");
-                        hangupAndPowerOff();
-                        return;
-                    }
-                    if (mPendingRadioPowerOffAfterDataOff &&
-                            (msg.arg1 == mPendingRadioPowerOffAfterDataOffTag)) {
-                        if (DBG) log("EVENT_SET_RADIO_OFF, turn radio off now.");
-                        hangupAndPowerOff();
-                        mPendingRadioPowerOffAfterDataOffTag += 1;
-                        mPendingRadioPowerOffAfterDataOff = false;
-                    } else {
-                        log("EVENT_SET_RADIO_OFF is stale arg1=" + msg.arg1 +
-                                "!= tag=" + mPendingRadioPowerOffAfterDataOffTag);
-                    }
+                    mPendingRadioPowerOffAfterDataOff = false;
+                    log("Wait for all data networks torn down timed out. Power off now.");
+                    hangupAndPowerOff();
                 }
                 break;
 
@@ -1223,8 +1221,8 @@
                     mCdnr.updateEfFromUsim(null /* Usim */);
                 }
                 onUpdateIccAvailability();
-                if (mUiccApplcation == null
-                        || mUiccApplcation.getState() != AppState.APPSTATE_READY) {
+                if (mUiccApplication == null
+                        || mUiccApplication.getState() != AppState.APPSTATE_READY) {
                     mIsSimReady = false;
                     updateSpnDisplay();
                 }
@@ -1471,45 +1469,28 @@
                 }
                 break;
 
-            case EVENT_REGISTER_DATA_NETWORK_EXISTING_CHANGED: {
-                mPhone.getDataNetworkController().registerDataNetworkControllerCallback(
-                        new DataNetworkControllerCallback(this::post) {
-                        @Override
-                        public void onAnyDataNetworkExistingChanged(boolean anyDataExisting) {
-                            if (mAnyDataExisting != anyDataExisting) {
-                                mAnyDataExisting = anyDataExisting;
-                                log("onAnyDataNetworkExistingChanged: anyDataExisting="
-                                        + anyDataExisting);
-                                if (!mAnyDataExisting) {
-                                    sendEmptyMessage(EVENT_ALL_DATA_DISCONNECTED);
-                                }
-                            }
-                        }
-                        });
-                break;
-            }
             case EVENT_ALL_DATA_DISCONNECTED:
-                if (mPhone.isUsingNewDataStack()) {
-                    log("EVENT_ALL_DATA_DISCONNECTED");
-                    if (mPendingRadioPowerOffAfterDataOff) {
+                log("EVENT_ALL_DATA_DISCONNECTED");
+                synchronized (this) {
+                    if (!mPendingRadioPowerOffAfterDataOff) return;
+                    boolean areAllDataDisconnectedOnAllPhones = true;
+                    for (Phone phone : PhoneFactory.getPhones()) {
+                        if (phone.getDataNetworkController().areAllDataDisconnected()) {
+                            phone.getDataNetworkController()
+                                .unregisterDataNetworkControllerCallback(
+                                        mDataDisconnectedCallback);
+                        } else {
+                            log("Still waiting for all data disconnected on phone: "
+                                    + phone.getSubId());
+                            areAllDataDisconnectedOnAllPhones = false;
+                        }
+                    }
+                    if (areAllDataDisconnectedOnAllPhones) {
                         mPendingRadioPowerOffAfterDataOff = false;
                         removeMessages(EVENT_SET_RADIO_POWER_OFF);
-                        if (DBG) log("EVENT_ALL_DATA_DISCONNECTED, turn radio off now.");
+                        if (DBG) log("Data disconnected for all phones, turn radio off now.");
                         hangupAndPowerOff();
                     }
-                    return;
-                }
-                int dds = SubscriptionManager.getDefaultDataSubscriptionId();
-                ProxyController.getInstance().unregisterForAllDataDisconnected(dds, this);
-                synchronized(this) {
-                    if (mPendingRadioPowerOffAfterDataOff) {
-                        if (DBG) log("EVENT_ALL_DATA_DISCONNECTED, turn radio off now.");
-                        hangupAndPowerOff();
-                        mPendingRadioPowerOffAfterDataOffTag += 1;
-                        mPendingRadioPowerOffAfterDataOff = false;
-                    } else {
-                        log("EVENT_ALL_DATA_DISCONNECTED is stale");
-                    }
                 }
                 break;
 
@@ -1666,7 +1647,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;
 
@@ -1675,8 +1657,8 @@
                 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()));
                     }
                     mLastPhysicalChannelConfigList = list;
                     boolean hasChanged = false;
@@ -1695,9 +1677,14 @@
                     // 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);
+                        ImsPhone imsPhone = (ImsPhone) mPhone.getImsPhone();
+                        if (imsPhone != null) {
+                            imsPhone.getImsStats().onServiceStateChanged(mSS);
+                        }
                         mServiceStateStats.onServiceStateChanged(mSS);
                     }
                 }
@@ -1718,10 +1705,6 @@
                 rspRspMsg.sendToTarget();
                 break;
 
-            case EVENT_CARRIER_CONFIG_CHANGED:
-                onCarrierConfigChanged();
-                break;
-
             case EVENT_POLL_STATE_REQUEST:
                 pollStateInternal(false);
                 break;
@@ -2106,16 +2089,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;
     }
@@ -2146,6 +2133,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;
@@ -2157,18 +2149,8 @@
 
     private boolean isInternetPhysicalChannelConfig(PhysicalChannelConfig config) {
         for (int cid : config.getContextIds()) {
-            if (mPhone.isUsingNewDataStack()) {
-                if (mPhone.getDataNetworkController().isInternetNetwork(cid)) {
-                    return true;
-                }
-            } else {
-                DataConnection dc = mPhone.getDcTracker(
-                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                        .getDataConnectionByContextId(cid);
-                if (dc != null && dc.getNetworkCapabilities().hasCapability(
-                        NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
-                    return true;
-                }
+            if (mPhone.getDataNetworkController().isInternetNetwork(cid)) {
+                return true;
             }
         }
         return false;
@@ -2195,14 +2177,14 @@
         if (wlanPsRegState != null
                 && wlanPsRegState.getAccessNetworkTechnology()
                 == TelephonyManager.NETWORK_TYPE_IWLAN
-                && wlanPsRegState.getRegistrationState()
+                && wlanPsRegState.getNetworkRegistrationState()
                 == NetworkRegistrationInfo.REGISTRATION_STATE_HOME
                 && isIwlanPreferred) {
             serviceState.setDataRegState(ServiceState.STATE_IN_SERVICE);
         } else if (wwanPsRegState != null) {
             // If the device is not camped on IWLAN, then we use cellular PS registration state
             // to compute reg state and rat.
-            int regState = wwanPsRegState.getRegistrationState();
+            int regState = wwanPsRegState.getNetworkRegistrationState();
             serviceState.setDataRegState(regCodeToServiceState(regState));
         }
         if (DBG) {
@@ -2218,10 +2200,8 @@
                 VoiceSpecificRegistrationInfo voiceSpecificStates =
                         networkRegState.getVoiceSpecificInfo();
 
-                int registrationState = networkRegState.getRegistrationState();
+                int registrationState = networkRegState.getNetworkRegistrationState();
                 int cssIndicator = voiceSpecificStates.cssSupported ? 1 : 0;
-                int newVoiceRat = ServiceState.networkTypeToRilRadioTechnology(
-                        networkRegState.getAccessNetworkTechnology());
                 mNewSS.setVoiceRegState(regCodeToServiceState(registrationState));
                 mNewSS.setCssIndicator(cssIndicator);
                 mNewSS.addNetworkRegistrationInfo(networkRegState);
@@ -2254,7 +2234,7 @@
                                     && !isRoamIndForHomeSystem(roamingIndicator);
                     mNewSS.setVoiceRoaming(cdmaRoaming);
                     mRoamingIndicator = roamingIndicator;
-                    mIsInPrl = (systemIsInPrl == 0) ? false : true;
+                    mIsInPrl = systemIsInPrl != 0;
                     mDefaultRoamingIndicator = defaultRoamingIndicator;
 
                     int systemId = 0;
@@ -2300,7 +2280,7 @@
                 mNewSS.addNetworkRegistrationInfo(networkRegState);
                 DataSpecificRegistrationInfo dataSpecificStates =
                         networkRegState.getDataSpecificInfo();
-                int registrationState = networkRegState.getRegistrationState();
+                int registrationState = networkRegState.getNetworkRegistrationState();
                 int serviceState = regCodeToServiceState(registrationState);
                 int newDataRat = ServiceState.networkTypeToRilRadioTechnology(
                         networkRegState.getAccessNetworkTechnology());
@@ -2631,8 +2611,7 @@
      */
     private boolean isRoamIndForHomeSystem(int roamInd) {
         // retrieve the carrier-specified list of ERIs for home system
-        final PersistableBundle config = getCarrierConfig();
-        int[] homeRoamIndicators = config.getIntArray(CarrierConfigManager
+        int[] homeRoamIndicators = mCarrierConfig.getIntArray(CarrierConfigManager
                     .KEY_CDMA_ENHANCED_ROAMING_INDICATOR_FOR_HOME_NETWORK_INT_ARRAY);
 
         log("isRoamIndForHomeSystem: homeRoamIndicators=" + Arrays.toString(homeRoamIndicators));
@@ -2661,8 +2640,6 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     protected void updateRoamingState() {
-        PersistableBundle bundle = getCarrierConfig();
-
         if (mPhone.isPhoneTypeGsm()) {
             /**
              * Since the roaming state of gsm service (from +CREG) and
@@ -2687,14 +2664,14 @@
                 roaming = false;
             }
 
-            if (alwaysOnHomeNetwork(bundle)) {
+            if (alwaysOnHomeNetwork(mCarrierConfig)) {
                 log("updateRoamingState: carrier config override always on home network");
                 roaming = false;
-            } else if (isNonRoamingInGsmNetwork(bundle, mNewSS.getOperatorNumeric())) {
+            } else if (isNonRoamingInGsmNetwork(mCarrierConfig, mNewSS.getOperatorNumeric())) {
                 log("updateRoamingState: carrier config override set non roaming:"
                         + mNewSS.getOperatorNumeric());
                 roaming = false;
-            } else if (isRoamingInGsmNetwork(bundle, mNewSS.getOperatorNumeric())) {
+            } else if (isRoamingInGsmNetwork(mCarrierConfig, mNewSS.getOperatorNumeric())) {
                 log("updateRoamingState: carrier config override set roaming:"
                         + mNewSS.getOperatorNumeric());
                 roaming = true;
@@ -2704,16 +2681,16 @@
         } else {
             String systemId = Integer.toString(mNewSS.getCdmaSystemId());
 
-            if (alwaysOnHomeNetwork(bundle)) {
+            if (alwaysOnHomeNetwork(mCarrierConfig)) {
                 log("updateRoamingState: carrier config override always on home network");
                 setRoamingOff();
-            } else if (isNonRoamingInGsmNetwork(bundle, mNewSS.getOperatorNumeric())
-                    || isNonRoamingInCdmaNetwork(bundle, systemId)) {
+            } else if (isNonRoamingInGsmNetwork(mCarrierConfig, mNewSS.getOperatorNumeric())
+                    || isNonRoamingInCdmaNetwork(mCarrierConfig, systemId)) {
                 log("updateRoamingState: carrier config override set non-roaming:"
                         + mNewSS.getOperatorNumeric() + ", " + systemId);
                 setRoamingOff();
-            } else if (isRoamingInGsmNetwork(bundle, mNewSS.getOperatorNumeric())
-                    || isRoamingInCdmaNetwork(bundle, systemId)) {
+            } else if (isRoamingInGsmNetwork(mCarrierConfig, mNewSS.getOperatorNumeric())
+                    || isRoamingInCdmaNetwork(mCarrierConfig, systemId)) {
                 log("updateRoamingState: carrier config override set roaming:"
                         + mNewSS.getOperatorNumeric() + ", " + systemId);
                 setRoamingOn();
@@ -2744,24 +2721,21 @@
         if (!mPhone.isPhoneTypeGsm() && !mSS.getRoaming()) {
             boolean hasBrandOverride = mUiccController.getUiccPort(getPhoneId()) != null
                     && mUiccController.getUiccPort(getPhoneId()).getOperatorBrandOverride() != null;
-            if (!hasBrandOverride) {
-                PersistableBundle config = getCarrierConfig();
-                if (config.getBoolean(
+            if (!hasBrandOverride && mCarrierConfig.getBoolean(
                         CarrierConfigManager.KEY_CDMA_HOME_REGISTERED_PLMN_NAME_OVERRIDE_BOOL)) {
-                    String operator = config.getString(
-                            CarrierConfigManager.KEY_CDMA_HOME_REGISTERED_PLMN_NAME_STRING);
-                    log("updateOperatorNameFromCarrierConfig: changing from "
-                            + mSS.getOperatorAlpha() + " to " + operator);
-                    // override long and short operator name, keeping numeric the same
-                    mSS.setOperatorName(operator, operator, mSS.getOperatorNumeric());
-                }
+                String operator = mCarrierConfig.getString(
+                        CarrierConfigManager.KEY_CDMA_HOME_REGISTERED_PLMN_NAME_STRING);
+                log("updateOperatorNameFromCarrierConfig: changing from "
+                        + mSS.getOperatorAlpha() + " to " + operator);
+                // override long and short operator name, keeping numeric the same
+                mSS.setOperatorName(operator, operator, mSS.getOperatorNumeric());
             }
         }
     }
 
     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
@@ -2791,13 +2765,12 @@
             SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
             mPhone.getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL);
 
-            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();
@@ -2805,6 +2778,26 @@
         mCurPlmn = data.getPlmn();
     }
 
+    @NonNull
+    private String getCarrierName(boolean showPlmn, String plmn, boolean showSpn, String spn) {
+        String carrierName = "";
+        if (showPlmn) {
+            carrierName = plmn;
+            if (showSpn) {
+                // Need to show both plmn and spn if both are not same.
+                if (!Objects.equals(spn, plmn)) {
+                    String separator = mPhone.getContext().getString(
+                            com.android.internal.R.string.kg_text_message_separator).toString();
+                    carrierName = new StringBuilder().append(carrierName).append(separator)
+                            .append(spn).toString();
+                }
+            }
+        } else if (showSpn) {
+            carrierName = spn;
+        }
+        return carrierName;
+    }
+
     private void updateSpnDisplayCdnr() {
         log("updateSpnDisplayCdnr+");
         CarrierDisplayNameData data = mCdnr.getCarrierDisplayNameData();
@@ -2815,8 +2808,8 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @VisibleForTesting
     public void updateSpnDisplay() {
-        PersistableBundle config = getCarrierConfig();
-        if (config.getBoolean(CarrierConfigManager.KEY_ENABLE_CARRIER_DISPLAY_NAME_RESOLVER_BOOL)) {
+        if (mCarrierConfig.getBoolean(
+                CarrierConfigManager.KEY_ENABLE_CARRIER_DISPLAY_NAME_RESOLVER_BOOL)) {
             updateSpnDisplayCdnr();
         } else {
             updateSpnDisplayLegacy();
@@ -2829,14 +2822,15 @@
         String spn = null;
         String dataSpn = null;
         boolean showSpn = false;
-        String plmn = null;
-        boolean showPlmn = false;
+        String plmn;
+        boolean showPlmn;
 
         String wfcVoiceSpnFormat = null;
         String wfcDataSpnFormat = null;
         String wfcFlightSpnFormat = null;
         int combinedRegState = getCombinedRegState(mSS);
         if (mPhone.getImsPhone() != null && mPhone.getImsPhone().isWifiCallingEnabled()
+                && mPhone.isImsRegistered()
                 && (combinedRegState == ServiceState.STATE_IN_SERVICE
                 && mSS.getDataNetworkType() == TelephonyManager.NETWORK_TYPE_IWLAN)) {
             // In Wi-Fi Calling mode (connected to WiFi and WFC enabled),
@@ -2847,20 +2841,17 @@
             //
             // 2) Show PLMN + Wi-Fi Calling if there is no valid SPN in case 1
 
-            int voiceIdx = 0;
-            int dataIdx = 0;
-            int flightModeIdx = -1;
-            boolean useRootLocale = false;
+            int voiceIdx;
+            int dataIdx;
+            int flightModeIdx;
+            boolean useRootLocale;
 
-            PersistableBundle bundle = getCarrierConfig();
-
-            voiceIdx = bundle.getInt(CarrierConfigManager.KEY_WFC_SPN_FORMAT_IDX_INT);
-            dataIdx = bundle.getInt(
-                    CarrierConfigManager.KEY_WFC_DATA_SPN_FORMAT_IDX_INT);
-            flightModeIdx = bundle.getInt(
+            voiceIdx = mCarrierConfig.getInt(CarrierConfigManager.KEY_WFC_SPN_FORMAT_IDX_INT);
+            dataIdx = mCarrierConfig.getInt(CarrierConfigManager.KEY_WFC_DATA_SPN_FORMAT_IDX_INT);
+            flightModeIdx = mCarrierConfig.getInt(
                     CarrierConfigManager.KEY_WFC_FLIGHT_MODE_SPN_FORMAT_IDX_INT);
             useRootLocale =
-                    bundle.getBoolean(CarrierConfigManager.KEY_WFC_SPN_USE_ROOT_LOCALE);
+                    mCarrierConfig.getBoolean(CarrierConfigManager.KEY_WFC_SPN_USE_ROOT_LOCALE);
 
             String[] wfcSpnFormats = SubscriptionManager.getResourcesForSubId(mPhone.getContext(),
                     mPhone.getSubId(), useRootLocale)
@@ -2891,21 +2882,19 @@
                 && (mPhone.getImsPhone() != null)
                 && (mPhone.getImsPhone().getImsRegistrationTech()
                 == ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM)) {
-            // In Cros SIM Calling mode show SPN or PLMN + Cross SIM Calling
+            // In Cross SIM Calling mode show SPN or PLMN + Cross SIM Calling
             //
             // 1) Show SPN + Cross SIM Calling If SIM has SPN and SPN display condition
             //    is satisfied or SPN override is enabled for this carrier
             //
             // 2) Show PLMN + Cross SIM Calling if there is no valid SPN in case 1
-            PersistableBundle bundle = getCarrierConfig();
             int crossSimSpnFormatIdx =
-                    bundle.getInt(CarrierConfigManager.KEY_CROSS_SIM_SPN_FORMAT_INT);
+                    mCarrierConfig.getInt(CarrierConfigManager.KEY_CROSS_SIM_SPN_FORMAT_INT);
             boolean useRootLocale =
-                    bundle.getBoolean(CarrierConfigManager.KEY_WFC_SPN_USE_ROOT_LOCALE);
+                    mCarrierConfig.getBoolean(CarrierConfigManager.KEY_WFC_SPN_USE_ROOT_LOCALE);
 
             String[] crossSimSpnFormats = SubscriptionManager.getResourcesForSubId(
-                    mPhone.getContext(),
-                    mPhone.getSubId(), useRootLocale)
+                    mPhone.getContext(), mPhone.getSubId(), useRootLocale)
                     .getStringArray(R.array.crossSimSpnFormats);
 
             if (crossSimSpnFormatIdx < 0 || crossSimSpnFormatIdx >= crossSimSpnFormats.length) {
@@ -2935,7 +2924,6 @@
             //    EXTRA_SHOW_PLMN = true
             //    EXTRA_PLMN = null
 
-            IccRecords iccRecords = mIccRecords;
             int rule = getCarrierNameDisplayBitmask(mSS);
             boolean noService = false;
             if (combinedRegState == ServiceState.STATE_OUT_OF_SERVICE
@@ -2951,8 +2939,7 @@
                 } else {
                     // No service at all
                     plmn = Resources.getSystem()
-                            .getText(
-                                com.android.internal.R.string.lockscreen_carrier_default)
+                            .getText(com.android.internal.R.string.lockscreen_carrier_default)
                             .toString();
                     noService = true;
                 }
@@ -2995,8 +2982,7 @@
                 } else if (!TextUtils.isEmpty(plmn)) {
                     // Show PLMN + Cross-SIM Calling if there is no valid SPN in the above case
                     String originalPlmn = plmn.trim();
-                    PersistableBundle config = getCarrierConfig();
-                    if (mIccRecords != null && config.getBoolean(
+                    if (mIccRecords != null && mCarrierConfig.getBoolean(
                             CarrierConfigManager.KEY_WFC_CARRIER_NAME_OVERRIDE_BY_PNN_BOOL)) {
                         originalPlmn = mIccRecords.getPnnHomeName();
                     }
@@ -3021,8 +3007,7 @@
                 // Show PLMN + Wi-Fi Calling if there is no valid SPN in the above case
                 String originalPlmn = plmn.trim();
 
-                PersistableBundle config = getCarrierConfig();
-                if (mIccRecords != null && config.getBoolean(
+                if (mIccRecords != null && mCarrierConfig.getBoolean(
                         CarrierConfigManager.KEY_WFC_CARRIER_NAME_OVERRIDE_BY_PNN_BOOL)) {
                     originalPlmn = mIccRecords.getPnnHomeName();
                 }
@@ -3105,12 +3090,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);
         }
@@ -3122,22 +3107,13 @@
         }
 
         // 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 it's on and available and we want it off gracefully
-            if (!mPhone.isUsingNewDataStack() && mImsRegistrationOnOff
-                    && getRadioPowerOffDelayTimeoutForImsRegistration() > 0) {
-                if (DBG) log("setPowerStateToDesired: delaying power off until IMS dereg.");
-                startDelayRadioOffWaitingForImsDeregTimeout();
-                // Return early here as we do not want to hit the cancel timeout code below.
-                return;
-            } else {
-                if (DBG) log("setPowerStateToDesired: powerOffRadioSafely()");
-                powerOffRadioSafely();
-            }
+            if (DBG) log("setPowerStateToDesired: powerOffRadioSafely()");
+            powerOffRadioSafely();
         } else if (mDeviceShuttingDown
                 && (mCi.getRadioState() != TelephonyManager.RADIO_POWER_UNAVAILABLE)) {
             // !mDesiredPowerState condition above will happen first if the radio is on, so we will
@@ -3151,7 +3127,6 @@
 
     /**
      * Cancel the EVENT_POWER_OFF_RADIO_DELAYED event if it is currently pending to be completed.
-     * @return true if there was a pending timeout message in the queue, false otherwise.
      */
     private void cancelDelayRadioOffWaitingForImsDeregTimeout() {
         if (hasMessages(EVENT_POWER_OFF_RADIO_IMS_DEREG_TIMEOUT)) {
@@ -3160,21 +3135,6 @@
         }
     }
 
-    /**
-     * Start a timer to turn off the radio if IMS does not move to deregistered after the
-     * radio power off event occurred. If this event already exists in the message queue, then
-     * ignore the new request and use the existing one.
-     */
-    private void startDelayRadioOffWaitingForImsDeregTimeout() {
-        if (hasMessages(EVENT_POWER_OFF_RADIO_IMS_DEREG_TIMEOUT)) {
-            if (DBG) log("startDelayRadioOffWaitingForImsDeregTimeout: timer exists, ignoring");
-            return;
-        }
-        if (DBG) log("startDelayRadioOffWaitingForImsDeregTimeout: starting timer");
-        sendEmptyMessageDelayed(EVENT_POWER_OFF_RADIO_IMS_DEREG_TIMEOUT,
-                getRadioPowerOffDelayTimeoutForImsRegistration());
-    }
-
     protected void onUpdateIccAvailability() {
         if (mUiccController == null ) {
             return;
@@ -3182,7 +3142,7 @@
 
         UiccCardApplication newUiccApplication = getUiccCardApplication();
 
-        if (mUiccApplcation != newUiccApplication) {
+        if (mUiccApplication != newUiccApplication) {
 
             // Remove the EF records that come from UICC
             if (mIccRecords instanceof SIMRecords) {
@@ -3191,26 +3151,26 @@
                 mCdnr.updateEfFromRuim(null /* ruim */);
             }
 
-            if (mUiccApplcation != null) {
+            if (mUiccApplication != null) {
                 log("Removing stale icc objects.");
-                mUiccApplcation.unregisterForReady(this);
+                mUiccApplication.unregisterForReady(this);
                 if (mIccRecords != null) {
                     mIccRecords.unregisterForRecordsLoaded(this);
                 }
                 mIccRecords = null;
-                mUiccApplcation = null;
+                mUiccApplication = null;
             }
             if (newUiccApplication != null) {
                 log("New card found");
-                mUiccApplcation = newUiccApplication;
-                mIccRecords = mUiccApplcation.getIccRecords();
+                mUiccApplication = newUiccApplication;
+                mIccRecords = mUiccApplication.getIccRecords();
                 if (mPhone.isPhoneTypeGsm()) {
-                    mUiccApplcation.registerForReady(this, EVENT_SIM_READY, null);
+                    mUiccApplication.registerForReady(this, EVENT_SIM_READY, null);
                     if (mIccRecords != null) {
                         mIccRecords.registerForRecordsLoaded(this, EVENT_SIM_RECORDS_LOADED, null);
                     }
                 } else if (mIsSubscriptionFromRuim) {
-                    mUiccApplcation.registerForReady(this, EVENT_RUIM_READY, null);
+                    mUiccApplication.registerForReady(this, EVENT_RUIM_READY, null);
                     if (mIccRecords != null) {
                         mIccRecords.registerForRecordsLoaded(this, EVENT_RUIM_RECORDS_LOADED, null);
                     }
@@ -3297,7 +3257,6 @@
                 + " mImsRegistrationOnOff=" + mImsRegistrationOnOff
                 + "}");
 
-
         if (mImsRegistrationOnOff && !registered) {
             // moving to deregistered, only send this event if we need to re-evaluate
             if (getRadioPowerOffDelayTimeoutForImsRegistration() > 0) {
@@ -3310,6 +3269,9 @@
             }
         }
         mImsRegistrationOnOff = registered;
+
+        // It's possible ServiceState changes did not trigger SPN display update; we update it here.
+        updateSpnDisplay();
     }
 
     public void onImsCapabilityChanged() {
@@ -3346,7 +3308,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);
@@ -3363,7 +3325,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);
@@ -3455,20 +3417,21 @@
         useDataRegStateForDataOnlyDevices();
         processIwlanRegistrationInfo();
 
-        if (TelephonyUtils.IS_DEBUGGABLE && mPhone.mTelephonyTester != null) {
-            mPhone.mTelephonyTester.overrideServiceState(mNewSS);
+        updateNrFrequencyRangeFromPhysicalChannelConfigs(mLastPhysicalChannelConfigList, mNewSS);
+        updateNrStateFromPhysicalChannelConfigs(mLastPhysicalChannelConfigList, mNewSS);
+
+        if (TelephonyUtils.IS_DEBUGGABLE && mPhone.getTelephonyTester() != null) {
+            mPhone.getTelephonyTester().overrideServiceState(mNewSS);
         }
 
         NetworkRegistrationInfo networkRegState = mNewSS.getNetworkRegistrationInfo(
                 NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-        updateNrFrequencyRangeFromPhysicalChannelConfigs(mLastPhysicalChannelConfigList, mNewSS);
-        updateNrStateFromPhysicalChannelConfigs(mLastPhysicalChannelConfigList, mNewSS);
         setPhyCellInfoFromCellIdentity(mNewSS, networkRegState.getCellIdentity());
 
         if (DBG) {
-            log("Poll ServiceState done: "
-                    + " oldSS=[" + mSS + "] newSS=[" + mNewSS + "]"
-                    + " oldMaxDataCalls=" + mMaxDataCalls
+            log("Poll ServiceState done: oldSS=" + mSS);
+            log("Poll ServiceState done: newSS=" + mNewSS);
+            log("Poll ServiceState done: oldMaxDataCalls=" + mMaxDataCalls
                     + " mNewMaxDataCalls=" + mNewMaxDataCalls
                     + " oldReasonDataDenied=" + mReasonDataDenied
                     + " mNewReasonDataDenied=" + mNewReasonDataDenied);
@@ -3489,14 +3452,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 =
@@ -3510,11 +3469,6 @@
             NetworkRegistrationInfo newNrs = mNewSS.getNetworkRegistrationInfo(
                     NetworkRegistrationInfo.DOMAIN_PS, transport);
 
-            // If the previously it was not in service, and now it's in service, trigger the
-            // attached event. Also if airplane mode was just turned on, and data is already in
-            // service, we need to trigger the attached event again so that DcTracker can setup
-            // data on all connectable APNs again (because we've already torn down all data
-            // connections just before airplane mode turned on)
             boolean changed = (oldNrs == null || !oldNrs.isInService() || hasAirplaneModeOnChanged)
                     && (newNrs != null && newNrs.isInService());
             hasDataAttached.put(transport, changed);
@@ -3539,9 +3493,9 @@
                 anyDataRatChanged = true;
             }
 
-            int oldRegState = oldNrs != null ? oldNrs.getRegistrationState()
+            int oldRegState = oldNrs != null ? oldNrs.getNetworkRegistrationState()
                     : NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN;
-            int newRegState = newNrs != null ? newNrs.getRegistrationState()
+            int newRegState = newNrs != null ? newNrs.getNetworkRegistrationState()
                     : NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN;
             hasDataRegStateChanged.put(transport, oldRegState != newRegState);
             if (oldRegState != newRegState) {
@@ -3598,8 +3552,6 @@
 
         boolean hasCssIndicatorChanged = (mSS.getCssIndicator() != mNewSS.getCssIndicator());
 
-        boolean hasBandwidthChanged = mSS.getCellBandwidths() != mNewSS.getCellBandwidths();
-
         boolean has4gHandoff = false;
         boolean hasMultiApnSupport = false;
         boolean hasLostMultiApnSupport = false;
@@ -3643,7 +3595,6 @@
                     + " hasCssIndicatorChanged = " + hasCssIndicatorChanged
                     + " hasNrFrequencyRangeChanged = " + hasNrFrequencyRangeChanged
                     + " hasNrStateChanged = " + hasNrStateChanged
-                    + " hasBandwidthChanged = " + hasBandwidthChanged
                     + " hasAirplaneModeOnlChanged = " + hasAirplaneModeOnChanged);
         }
 
@@ -3690,7 +3641,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) {
@@ -3736,10 +3687,6 @@
             mCssIndicatorChangedRegistrants.notifyRegistrants();
         }
 
-        if (hasBandwidthChanged) {
-            mBandwidthChangedRegistrants.notifyRegistrants();
-        }
-
         if (hasRejectCauseChanged) {
             setNotification(CS_REJECT_CAUSE_ENABLED);
         }
@@ -3775,8 +3722,10 @@
             // incomplete.
             // CellIdentity can return a null MCC and MNC in CDMA
             String localeOperator = operatorNumeric;
-            if (isInvalidOperatorNumeric(operatorNumeric)
-                    || mSS.getDataNetworkType() == TelephonyManager.NETWORK_TYPE_IWLAN) {
+            if (mSS.getDataNetworkType() == TelephonyManager.NETWORK_TYPE_IWLAN) {
+                localeOperator = null;
+            }
+            if (isInvalidOperatorNumeric(localeOperator)) {
                 for (CellIdentity cid : prioritizedCids) {
                     if (!TextUtils.isEmpty(cid.getPlmn())) {
                         localeOperator = cid.getPlmn();
@@ -3817,6 +3766,10 @@
 
             TelephonyMetrics.getInstance().writeServiceStateChanged(mPhone.getPhoneId(), mSS);
             mPhone.getVoiceCallSessionStats().onServiceStateChanged(mSS);
+            ImsPhone imsPhone = (ImsPhone) mPhone.getImsPhone();
+            if (imsPhone != null) {
+                imsPhone.getImsStats().onServiceStateChanged(mSS);
+            }
             mServiceStateStats.onServiceStateChanged(mSS);
         }
 
@@ -3982,8 +3935,9 @@
                 }
             }
 
-            if (mUiccApplcation != null && mUiccApplcation.getState() == AppState.APPSTATE_READY &&
-                    mIccRecords != null && getCombinedRegState(mSS) == ServiceState.STATE_IN_SERVICE
+            if (mUiccApplication != null && mUiccApplication.getState() == AppState.APPSTATE_READY
+                    && mIccRecords != null
+                    && getCombinedRegState(mSS) == ServiceState.STATE_IN_SERVICE
                     && !ServiceState.isPsOnlyTech(mSS.getRilVoiceRadioTechnology())) {
                 // SIM is found on the device. If ERI roaming is OFF, and SID/NID matches
                 // one configured in SIM, use operator name from CSIM record. Note that ERI, SID,
@@ -4014,10 +3968,9 @@
         }
 
         String carrierName = mIccRecords != null ? mIccRecords.getServiceProviderName() : "";
-        PersistableBundle config = getCarrierConfig();
-        if (config.getBoolean(CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL)
+        if (mCarrierConfig.getBoolean(CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL)
                 || TextUtils.isEmpty(carrierName)) {
-            return config.getString(CarrierConfigManager.KEY_CARRIER_NAME_STRING);
+            return mCarrierConfig.getString(CarrierConfigManager.KEY_CARRIER_NAME_STRING);
         }
 
         return carrierName;
@@ -4036,7 +3989,6 @@
      */
     @CarrierNameDisplayBitmask
     public int getCarrierNameDisplayBitmask(ServiceState ss) {
-        PersistableBundle config = getCarrierConfig();
         if (!TextUtils.isEmpty(getOperatorBrandOverride())) {
             // If the operator has been overridden, all PLMNs will be considered HOME PLMNs, only
             // show SPN.
@@ -4046,7 +3998,7 @@
             // This is a hack from IccRecords#getServiceProviderName().
             return CARRIER_NAME_DISPLAY_BITMASK_SHOW_PLMN;
         } else {
-            boolean useRoamingFromServiceState = config.getBoolean(
+            boolean useRoamingFromServiceState = mCarrierConfig.getBoolean(
                     CarrierConfigManager.KEY_SPN_DISPLAY_RULE_USE_ROAMING_FROM_SERVICE_STATE_BOOL);
             int carrierDisplayNameConditionFromSim =
                     mIccRecords == null ? 0 : mIccRecords.getCarrierNameDisplayCondition();
@@ -4253,7 +4205,7 @@
     }
 
     /**
-     * Do not set roaming state in case of oprators considered non-roaming.
+     * Do not set roaming state in case of operators considered non-roaming.
      *
      * Can use mcc or mcc+mnc as item of
      * {@link CarrierConfigManager#KEY_NON_ROAMING_OPERATOR_STRING_ARRAY}.
@@ -4266,8 +4218,7 @@
     private boolean isOperatorConsideredNonRoaming(ServiceState s) {
         String operatorNumeric = s.getOperatorNumeric();
 
-        PersistableBundle config = getCarrierConfig();
-        String[] numericArray = config.getStringArray(
+        String[] numericArray = mCarrierConfig.getStringArray(
                 CarrierConfigManager.KEY_NON_ROAMING_OPERATOR_STRING_ARRAY);
 
         if (ArrayUtils.isEmpty(numericArray) || operatorNumeric == null) {
@@ -4284,8 +4235,7 @@
 
     private boolean isOperatorConsideredRoaming(ServiceState s) {
         String operatorNumeric = s.getOperatorNumeric();
-        PersistableBundle config = getCarrierConfig();
-        String[] numericArray = config.getStringArray(
+        String[] numericArray = mCarrierConfig.getStringArray(
                 CarrierConfigManager.KEY_ROAMING_OPERATOR_STRING_ARRAY);
         if (ArrayUtils.isEmpty(numericArray) || operatorNumeric == null) {
             return false;
@@ -4318,12 +4268,12 @@
                     ((state & RILConstants.RIL_RESTRICTED_STATE_CS_EMERGENCY) != 0) ||
                             ((state & RILConstants.RIL_RESTRICTED_STATE_CS_ALL) != 0) );
             //ignore the normal call and data restricted state before SIM READY
-            if (mUiccApplcation != null && mUiccApplcation.getState() == AppState.APPSTATE_READY) {
+            if (mUiccApplication != null
+                    && mUiccApplication.getState() == AppState.APPSTATE_READY) {
                 newRs.setCsNormalRestricted(
                         ((state & RILConstants.RIL_RESTRICTED_STATE_CS_NORMAL) != 0) ||
                                 ((state & RILConstants.RIL_RESTRICTED_STATE_CS_ALL) != 0) );
-                newRs.setPsRestricted(
-                        (state & RILConstants.RIL_RESTRICTED_STATE_PS_ALL)!= 0);
+                newRs.setPsRestricted((state & RILConstants.RIL_RESTRICTED_STATE_PS_ALL) != 0);
             }
 
             if (DBG) log("onRestrictedStateChanged: new rs "+ newRs);
@@ -4537,12 +4487,9 @@
         }
         Context context = mPhone.getContext();
 
-        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)) {
+        SubscriptionInfoInternal subInfo = mSubscriptionManagerService
+                .getSubscriptionInfoInternal(mPhone.getSubId());
+        if (subInfo == null || !subInfo.isVisible()) {
             log("cannot setNotification on invisible subid mSubId=" + mSubId);
             return;
         }
@@ -4555,10 +4502,8 @@
             return;
         }
 
-        boolean autoCancelCsRejectNotification = false;
-
-        PersistableBundle bundle = getCarrierConfig();
-        boolean disableVoiceBarringNotification = bundle.getBoolean(
+        boolean autoCancelCsRejectNotification;
+        boolean disableVoiceBarringNotification = mCarrierConfig.getBoolean(
                 CarrierConfigManager.KEY_DISABLE_VOICE_BARRING_NOTIFICATION_BOOL, false);
         if (disableVoiceBarringNotification && (notifyType == CS_ENABLED
                 || notifyType == CS_NORMAL_ENABLED
@@ -4566,7 +4511,7 @@
             if (DBG) log("Voice/emergency call barred notification disabled");
             return;
         }
-        autoCancelCsRejectNotification = bundle.getBoolean(
+        autoCancelCsRejectNotification = mCarrierConfig.getBoolean(
                 CarrierConfigManager.KEY_AUTO_CANCEL_CS_REJECT_NOTIFICATION, false);
 
         CharSequence details = "";
@@ -4576,7 +4521,7 @@
 
         final boolean multipleSubscriptions = (((TelephonyManager) mPhone.getContext()
                   .getSystemService(Context.TELEPHONY_SERVICE)).getPhoneCount() > 1);
-        final int simNumber = mSubscriptionController.getSlotIndex(mSubId) + 1;
+        int simNumber = SubscriptionManager.getSlotIndex(mSubId) + 1;
 
         switch (notifyType) {
             case PS_ENABLED:
@@ -4987,9 +4932,10 @@
      *
      * @param h handler to notify
      * @param what what code of message when delivered
+     * @param userobj the user obj that will be passed back when notify
      */
-    public void registerForServiceStateChanged(Handler h, int what) {
-        mServiceStateChangedRegistrants.addUnique(h, what, null);
+    public void registerForServiceStateChanged(Handler h, int what, Object userobj) {
+        mServiceStateChangedRegistrants.addUnique(h, what, userobj);
     }
 
     /**
@@ -5009,79 +4955,33 @@
     public void powerOffRadioSafely() {
         synchronized (this) {
             if (!mPendingRadioPowerOffAfterDataOff) {
-                if (mPhone.isUsingNewDataStack()) {
-                    if (mAnyDataExisting) {
-                        log("powerOffRadioSafely: Tear down all data networks.");
-                        mPhone.getDataNetworkController().tearDownAllDataNetworks(
-                                DataNetwork.TEAR_DOWN_REASON_AIRPLANE_MODE_ON);
-                        sendEmptyMessageDelayed(EVENT_SET_RADIO_POWER_OFF,
-                                POWER_OFF_ALL_DATA_NETWORKS_DISCONNECTED_TIMEOUT);
-                    } else {
-                        log("powerOffRadioSafely: No data is connected.");
-                        sendEmptyMessage(EVENT_ALL_DATA_DISCONNECTED);
-                    }
-                    mPendingRadioPowerOffAfterDataOff = true;
-                    return;
+                // hang up all active voice calls first
+                if (mPhone.isPhoneTypeGsm() && mPhone.isInCall()) {
+                    mPhone.mCT.mRingingCall.hangupIfAlive();
+                    mPhone.mCT.mBackgroundCall.hangupIfAlive();
+                    mPhone.mCT.mForegroundCall.hangupIfAlive();
                 }
-                int dds = SubscriptionManager.getDefaultDataSubscriptionId();
-                // To minimize race conditions we call cleanUpAllConnections on
-                // both if else paths instead of before this isDisconnected test.
-                if (mPhone.areAllDataDisconnected()
-                        && (dds == mPhone.getSubId()
-                        || (dds != mPhone.getSubId()
-                        && ProxyController.getInstance().areAllDataDisconnected(dds)))) {
-                    // To minimize race conditions we do this after isDisconnected
-                    for (int transport : mAccessNetworksManager.getAvailableTransports()) {
-                        if (mPhone.getDcTracker(transport) != null) {
-                            mPhone.getDcTracker(transport).cleanUpAllConnections(
-                                    Phone.REASON_RADIO_TURNED_OFF);
-                        }
-                    }
-                    if (DBG) {
-                        log("powerOffRadioSafely: Data disconnected, turn off radio now.");
-                    }
-                    hangupAndPowerOff();
-                } else {
-                    // hang up all active voice calls first
-                    if (mPhone.isPhoneTypeGsm() && mPhone.isInCall()) {
-                        mPhone.mCT.mRingingCall.hangupIfAlive();
-                        mPhone.mCT.mBackgroundCall.hangupIfAlive();
-                        mPhone.mCT.mForegroundCall.hangupIfAlive();
-                    }
-                    for (int transport : mAccessNetworksManager.getAvailableTransports()) {
-                        if (mPhone.getDcTracker(transport) != null) {
-                            mPhone.getDcTracker(transport).cleanUpAllConnections(
-                                    Phone.REASON_RADIO_TURNED_OFF);
-                        }
-                    }
 
-                    if (dds != mPhone.getSubId()
-                            && !ProxyController.getInstance().areAllDataDisconnected(dds)) {
-                        if (DBG) {
-                            log(String.format("powerOffRadioSafely: Data is active on DDS (%d)."
-                                    + " Wait for all data disconnect", dds));
-                        }
-                        // Data is not disconnected on DDS. Wait for the data disconnect complete
-                        // before sending the RADIO_POWER off.
-                        ProxyController.getInstance().registerForAllDataDisconnected(dds, this,
-                                EVENT_ALL_DATA_DISCONNECTED);
+                for (Phone phone : PhoneFactory.getPhones()) {
+                    if (!phone.getDataNetworkController().areAllDataDisconnected()) {
+                        log("powerOffRadioSafely: Data is active on phone " + phone.getSubId()
+                                + ". Wait for all data disconnect.");
                         mPendingRadioPowerOffAfterDataOff = true;
+                        phone.getDataNetworkController().registerDataNetworkControllerCallback(
+                                mDataDisconnectedCallback);
                     }
-                    Message msg = Message.obtain(this);
-                    msg.what = EVENT_SET_RADIO_POWER_OFF;
-                    msg.arg1 = ++mPendingRadioPowerOffAfterDataOffTag;
-                    if (sendMessageDelayed(msg, 30000)) {
-                        if (DBG) {
-                            log("powerOffRadioSafely: Wait up to 30s for data to isconnect, then"
-                                    + " turn off radio.");
-                        }
-                        mPendingRadioPowerOffAfterDataOff = true;
-                    } else {
-                        log("powerOffRadioSafely: Cannot send delayed Msg, turn off radio right"
-                                + " away.");
-                        hangupAndPowerOff();
-                        mPendingRadioPowerOffAfterDataOff = false;
-                    }
+                }
+
+                // Tear down outside of the disconnected check to prevent race conditions.
+                mPhone.getDataNetworkController().tearDownAllDataNetworks(
+                        DataNetwork.TEAR_DOWN_REASON_AIRPLANE_MODE_ON);
+
+                if (mPendingRadioPowerOffAfterDataOff) {
+                    sendEmptyMessageDelayed(EVENT_SET_RADIO_POWER_OFF,
+                            POWER_OFF_ALL_DATA_NETWORKS_DISCONNECTED_TIMEOUT);
+                } else {
+                    log("powerOffRadioSafely: No data is connected, turn off radio now.");
+                    hangupAndPowerOff();
                 }
             }
         }
@@ -5097,7 +4997,6 @@
             if (mPendingRadioPowerOffAfterDataOff) {
                 if (DBG) log("Process pending request to turn radio off.");
                 hangupAndPowerOff();
-                mPendingRadioPowerOffAfterDataOffTag += 1;
                 mPendingRadioPowerOffAfterDataOff = false;
                 return true;
             }
@@ -5105,9 +5004,11 @@
         }
     }
 
-    private void onCarrierConfigChanged() {
-        PersistableBundle config = getCarrierConfig();
-        log("CarrierConfigChange " + config);
+    private void onCarrierConfigurationChanged(int slotIndex) {
+        if (slotIndex != mPhone.getPhoneId()) return;
+
+        mCarrierConfig = getCarrierConfig();
+        log("CarrierConfigChange " + mCarrierConfig);
 
         // Load the ERI based on carrier config. Carrier might have their specific ERI.
         if (mEriManager != null) {
@@ -5115,8 +5016,8 @@
             mCdnr.updateEfForEri(getOperatorNameFromEri());
         }
 
-        updateOperatorNamePattern(config);
-        mCdnr.updateEfFromCarrierConfig(config);
+        updateOperatorNamePattern(mCarrierConfig);
+        mCdnr.updateEfFromCarrierConfig(mCarrierConfig);
         mPhone.notifyCallForwardingIndicator();
 
         // Sometimes the network registration information comes before carrier config is ready.
@@ -5174,8 +5075,8 @@
     // Determine if the Icc card exists
     private boolean iccCardExists() {
         boolean iccCardExist = false;
-        if (mUiccApplcation != null) {
-            iccCardExist = mUiccApplcation.getState() != AppState.APPSTATE_UNKNOWN;
+        if (mUiccApplication != null) {
+            iccCardExist = mUiccApplication.getState() != AppState.APPSTATE_UNKNOWN;
         }
         return iccCardExist;
     }
@@ -5326,18 +5227,15 @@
         pw.println(" mNewSS=" + mNewSS);
         pw.println(" mVoiceCapable=" + mVoiceCapable);
         pw.println(" mRestrictedState=" + mRestrictedState);
-        pw.println(" mPollingContext=" + mPollingContext + " - " +
-                (mPollingContext != null ? mPollingContext[0] : ""));
+        pw.println(" mPollingContext=" + Arrays.toString(mPollingContext));
         pw.println(" mDesiredPowerState=" + mDesiredPowerState);
         pw.println(" mRestrictedState=" + mRestrictedState);
         pw.println(" mPendingRadioPowerOffAfterDataOff=" + mPendingRadioPowerOffAfterDataOff);
-        pw.println(" mPendingRadioPowerOffAfterDataOffTag=" + mPendingRadioPowerOffAfterDataOffTag);
         pw.println(" mCellIdentity=" + Rlog.pii(VDBG, mCellIdentity));
         pw.println(" mLastCellInfoReqTime=" + mLastCellInfoReqTime);
         dumpCellInfoList(pw);
         pw.flush();
         pw.println(" mAllowedNetworkTypes=" + mAllowedNetworkTypes);
-        pw.println(" mAnyDataExisting=" + mAnyDataExisting);
         pw.println(" mMaxDataCalls=" + mMaxDataCalls);
         pw.println(" mNewMaxDataCalls=" + mNewMaxDataCalls);
         pw.println(" mReasonDataDenied=" + mReasonDataDenied);
@@ -5366,8 +5264,8 @@
         pw.println(" mDefaultRoamingIndicator=" + mDefaultRoamingIndicator);
         pw.println(" mRegistrationState=" + mRegistrationState);
         pw.println(" mMdn=" + mMdn);
-        pw.println(" mHomeSystemId=" + mHomeSystemId);
-        pw.println(" mHomeNetworkId=" + mHomeNetworkId);
+        pw.println(" mHomeSystemId=" + Arrays.toString(mHomeSystemId));
+        pw.println(" mHomeNetworkId=" + Arrays.toString(mHomeNetworkId));
         pw.println(" mMin=" + mMin);
         pw.println(" mPrlVersion=" + mPrlVersion);
         pw.println(" mIsMinInfoReady=" + mIsMinInfoReady);
@@ -5381,9 +5279,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);
 
@@ -5605,14 +5502,8 @@
         return mPhone.getPhoneId();
     }
 
-    /* Reset Service state when IWLAN is enabled as polling in airplane mode
-     * causes state to go to OUT_OF_SERVICE state instead of STATE_OFF
-     */
-
-
     /**
-     * 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) {
@@ -5626,7 +5517,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()
@@ -5636,17 +5527,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
@@ -5656,31 +5536,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.getRegistrationState())
-                        .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_IWLAN)
-                        .setRejectCause(wwanNri.getRejectCause())
-                        .setEmergencyOnly(wwanNri.isEmergencyEnabled())
-                        .setAvailableServices(wwanNri.getAvailableServices())
-                        .build();
-                mNewSS.addNetworkRegistrationInfo(wlanNri);
-            }
-        }
     }
 
     /**
@@ -5938,25 +5793,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 fc378a2..59f7333 100644
--- a/src/java/com/android/internal/telephony/SignalStrengthController.java
+++ b/src/java/com/android/internal/telephony/SignalStrengthController.java
@@ -16,12 +16,11 @@
 
 package com.android.internal.telephony;
 
+import static android.telephony.TelephonyManager.HAL_SERVICE_NETWORK;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.IBinder;
@@ -46,6 +45,7 @@
 import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.util.ArrayUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.telephony.Rlog;
@@ -56,6 +56,7 @@
 import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
+import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.concurrent.TimeUnit;
@@ -96,7 +97,6 @@
     private static final int EVENT_POLL_SIGNAL_STRENGTH                     = 7;
     private static final int EVENT_SIGNAL_STRENGTH_UPDATE                   = 8;
     private static final int EVENT_POLL_SIGNAL_STRENGTH_DONE                = 9;
-    private static final int EVENT_CARRIER_CONFIG_CHANGED                   = 10;
 
     @NonNull
     private final Phone mPhone;
@@ -141,20 +141,6 @@
     @NonNull
     private final LocalLog mLocalLog = new LocalLog(64);
 
-    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(action)) {
-                int phoneId = intent.getExtras().getInt(CarrierConfigManager.EXTRA_SLOT_INDEX);
-                // Ignore the carrier config changed if the phoneId is not matched.
-                if (phoneId == mPhone.getPhoneId()) {
-                    sendEmptyMessage(EVENT_CARRIER_CONFIG_CHANGED);
-                }
-            }
-        }
-    };
-
     public SignalStrengthController(@NonNull Phone phone) {
         mPhone = phone;
         mCi = mPhone.mCi;
@@ -164,10 +150,12 @@
         mCi.setOnSignalStrengthUpdate(this, EVENT_SIGNAL_STRENGTH_UPDATE, null);
         setSignalStrengthDefaultValues();
 
+        CarrierConfigManager ccm = mPhone.getContext().getSystemService(CarrierConfigManager.class);
         mCarrierConfig = getCarrierConfig();
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        mPhone.getContext().registerReceiver(mBroadcastReceiver, filter);
+        // Callback which directly handle config change should be executed on handler thread
+        ccm.registerCarrierConfigChangeListener(this::post,
+                (slotIndex, subId, carrierId, specificCarrierId) ->
+                        onCarrierConfigurationChanged(slotIndex));
     }
 
     @Override
@@ -227,6 +215,11 @@
                 while (it.hasNext()) {
                     SignalRequestRecord srr = it.next();
                     if (srr.mRequest.getLiveToken().equals(record.mRequest.getLiveToken())) {
+                        try {
+                            srr.mRequest.getLiveToken().unlinkToDeath(srr, 0);
+                        } catch (NoSuchElementException ignored) {
+                            // Either never linked or has already unlinked, ignore anyway
+                        }
                         it.remove();
                     }
                 }
@@ -275,11 +268,6 @@
                 break;
             }
 
-            case EVENT_CARRIER_CONFIG_CHANGED: {
-                onCarrierConfigChanged();
-                break;
-            }
-
             default:
                 log("Unhandled message with number: " + msg.what);
                 break;
@@ -351,7 +339,7 @@
                 || (curTime - mSignalStrengthUpdatedTime > SIGNAL_STRENGTH_REFRESH_THRESHOLD_IN_MS);
         if (!isStale) return false;
 
-        List<SubscriptionInfo> subInfoList = SubscriptionController.getInstance()
+        List<SubscriptionInfo> subInfoList = SubscriptionManagerService.getInstance()
                 .getActiveSubscriptionInfoList(mPhone.getContext().getOpPackageName(),
                         mPhone.getContext().getAttributionTag());
 
@@ -417,7 +405,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) {
@@ -474,6 +462,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);
@@ -507,7 +507,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,
@@ -540,6 +540,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);
@@ -552,12 +558,13 @@
         for (SignalThresholdInfo signalThresholdInfo : signalThresholdInfos) {
             final int ran = signalThresholdInfo.getRadioAccessNetworkType();
             final int measurementType = signalThresholdInfo.getSignalMeasurementType();
-            final boolean isEnabledForSystem = signalThresholdInfo.isEnabled();
+            final boolean isEnabledForSystem =
+                    signalThresholdInfo.isEnabled() && shouldHonorSystemThresholds();
             int[] consolidatedThresholds =
                     getConsolidatedSignalThresholds(
                             ran,
                             measurementType,
-                            isEnabledForSystem && shouldHonorSystemThresholds()
+                            isEnabledForSystem
                                     ? signalThresholdInfo.getThresholds()
                                     : new int[]{},
                             ALIGNMENT_HYSTERESIS_DB);
@@ -567,12 +574,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());
@@ -583,6 +592,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();
@@ -731,7 +860,11 @@
                         && srr.mRequest.isSystemThresholdReportingRequestedWhileIdle());
     }
 
-    void onDeviceIdleStateChanged(boolean isDeviceIdle) {
+    /**
+     * Get notified when device idle state changed
+     */
+    @VisibleForTesting
+    public void onDeviceIdleStateChanged(boolean isDeviceIdle) {
         sendMessage(obtainMessage(EVENT_ON_DEVICE_IDLE_STATE_CHANGED, isDeviceIdle));
 
         localLog("onDeviceIdleStateChanged isDeviceIdle=" + isDeviceIdle);
@@ -968,7 +1101,9 @@
         return earfcnPairList;
     }
 
-    private void onCarrierConfigChanged() {
+    private void onCarrierConfigurationChanged(int slotIndex) {
+        if (slotIndex != mPhone.getPhoneId()) return;
+
         mCarrierConfig = getCarrierConfig();
         log("Carrier Config changed.");
 
@@ -1097,6 +1232,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 f3ece16..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,9 +55,11 @@
      * @param indicationType Type of radio indication
      */
     public void carrierInfoForImsiEncryption(int indicationType) {
-        mRil.processIndication(RIL.SIM_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_SIM, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION, null);
+        if (mRil.isLogOrTrace()) {
+            mRil.unsljLogRet(RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION, null);
+        }
 
         mRil.mCarrierInfoForImsiEncryptionRegistrants.notifyRegistrants(
                 new AsyncResult(null, null, null));
@@ -67,10 +71,12 @@
      * @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 (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED, response);
+        if (mRil.isLogOrTrace()) {
+            mRil.unsljLogRet(RIL_UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED, response);
+        }
 
         mRil.mCdmaSubscriptionChangedRegistrants.notifyRegistrants(
                 new AsyncResult(null, response, null));
@@ -81,9 +87,9 @@
      * @param indicationType Type of radio indication
      */
     public void simPhonebookChanged(int indicationType) {
-        mRil.processIndication(RIL.SIM_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_SIM, indicationType);
 
-        if (RIL.RILJ_LOGD) {
+        if (mRil.isLogOrTrace()) {
             mRil.unsljLog(RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED);
         }
 
@@ -98,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<>();
 
@@ -106,8 +112,9 @@
             simPhonebookRecords.add(RILUtils.convertHalPhonebookRecordInfo(record));
         }
 
-        if (RIL.RILJ_LOGD) {
-            mRil.unsljLogRet(RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED,
+        if (mRil.isLogOrTrace()) {
+            mRil.unsljLogRet(
+                    RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED,
                     "status = " + status + " received " + records.length + " records");
         }
 
@@ -122,14 +129,14 @@
      */
     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;
         response.efId = refreshResult.efId;
         response.aid = refreshResult.aid;
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_SIM_REFRESH, response);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_SIM_REFRESH, response);
 
         mRil.mIccRefreshRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
     }
@@ -139,9 +146,9 @@
      * @param indicationType Type of radio indication
      */
     public void simStatusChanged(int indicationType) {
-        mRil.processIndication(RIL.SIM_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_SIM, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED);
 
         mRil.mIccStatusChangedRegistrants.notifyRegistrants();
     }
@@ -154,9 +161,9 @@
      *        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 (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_STK_EVENT_NOTIFY);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_STK_EVENT_NOTIFY);
 
         if (mRil.mCatEventRegistrant != null) {
             mRil.mCatEventRegistrant.notifyRegistrant(new AsyncResult(null, cmd, null));
@@ -170,9 +177,9 @@
      *        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 (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_STK_PROACTIVE_COMMAND);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_STK_PROACTIVE_COMMAND);
 
         if (mRil.mCatProCmdRegistrant != null) {
             mRil.mCatProCmdRegistrant.notifyRegistrant(new AsyncResult(null, cmd, null));
@@ -184,9 +191,9 @@
      * @param indicationType Type of radio indication
      */
     public void stkSessionEnd(int indicationType) {
-        mRil.processIndication(RIL.SIM_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_SIM, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_STK_SESSION_END);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_STK_SESSION_END);
 
         if (mRil.mCatSessionEndRegistrant != null) {
             mRil.mCatSessionEndRegistrant.notifyRegistrant(new AsyncResult(null, null, null));
@@ -199,11 +206,13 @@
      * @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};
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_UICC_SUBSCRIPTION_STATUS_CHANGED, response);
+        if (mRil.isLogOrTrace()) {
+            mRil.unsljLogRet(RIL_UNSOL_UICC_SUBSCRIPTION_STATUS_CHANGED, response);
+        }
 
         mRil.mSubscriptionStatusRegistrants.notifyRegistrants(
                 new AsyncResult(null, response, null));
@@ -215,9 +224,9 @@
      * @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 (RIL.RILJ_LOGD) {
+        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/SlidingWindowEventCounter.java b/src/java/com/android/internal/telephony/SlidingWindowEventCounter.java
index 149e425..8fcc567 100644
--- a/src/java/com/android/internal/telephony/SlidingWindowEventCounter.java
+++ b/src/java/com/android/internal/telephony/SlidingWindowEventCounter.java
@@ -106,7 +106,11 @@
      * @return A string describing the anomaly event
      */
     public @NonNull String getFrequencyString() {
-        return String.format("%d times within %d ms.", mNumOccurrences, mWindowSizeMillis);
+        if (mWindowSizeMillis >= 1000L) {
+            return mNumOccurrences + " times within " + mWindowSizeMillis / 1000L + " seconds";
+        } else {
+            return mNumOccurrences + " times within " + mWindowSizeMillis + "ms";
+        }
     }
 
     @Override
diff --git a/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java b/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
index 7c52a42..ecd6276 100644
--- a/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
+++ b/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony;
 
+import static java.util.Map.entry;
+
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -32,6 +34,7 @@
 import com.android.internal.telephony.cdma.CdmaInboundSmsHandler;
 import com.android.internal.telephony.gsm.GsmInboundSmsHandler;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.telephony.Rlog;
 
 import java.util.HashMap;
@@ -72,19 +75,18 @@
 
     /** Mapping from DB COLUMN to PDU_PENDING_MESSAGE_PROJECTION index */
     static final Map<Integer, Integer> PDU_PENDING_MESSAGE_PROJECTION_INDEX_MAPPING =
-            new HashMap<Integer, Integer>() {{
-                put(InboundSmsHandler.PDU_COLUMN, 0);
-                put(InboundSmsHandler.SEQUENCE_COLUMN, 1);
-                put(InboundSmsHandler.DESTINATION_PORT_COLUMN, 2);
-                put(InboundSmsHandler.DATE_COLUMN, 3);
-                put(InboundSmsHandler.REFERENCE_NUMBER_COLUMN, 4);
-                put(InboundSmsHandler.COUNT_COLUMN, 5);
-                put(InboundSmsHandler.ADDRESS_COLUMN, 6);
-                put(InboundSmsHandler.ID_COLUMN, 7);
-                put(InboundSmsHandler.MESSAGE_BODY_COLUMN, 8);
-                put(InboundSmsHandler.DISPLAY_ADDRESS_COLUMN, 9);
-                put(InboundSmsHandler.SUBID_COLUMN, 10);
-            }};
+            Map.ofEntries(
+                entry(InboundSmsHandler.PDU_COLUMN, 0),
+                entry(InboundSmsHandler.SEQUENCE_COLUMN, 1),
+                entry(InboundSmsHandler.DESTINATION_PORT_COLUMN, 2),
+                entry(InboundSmsHandler.DATE_COLUMN, 3),
+                entry(InboundSmsHandler.REFERENCE_NUMBER_COLUMN, 4),
+                entry(InboundSmsHandler.COUNT_COLUMN, 5),
+                entry(InboundSmsHandler.ADDRESS_COLUMN, 6),
+                entry(InboundSmsHandler.ID_COLUMN, 7),
+                entry(InboundSmsHandler.MESSAGE_BODY_COLUMN, 8),
+                entry(InboundSmsHandler.DISPLAY_ADDRESS_COLUMN, 9),
+                entry(InboundSmsHandler.SUBID_COLUMN, 10));
 
 
     private static SmsBroadcastUndelivered instance;
@@ -258,10 +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();
-        // TODO consider other subs in this subId's group as well
-        int 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;
@@ -272,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 9f79161..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,6 +28,7 @@
 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;
@@ -40,6 +43,9 @@
 import android.telephony.TelephonyFrameworkInitializer;
 import android.telephony.TelephonyManager;
 
+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;
 
@@ -47,6 +53,7 @@
 import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.List;
+import java.util.Locale;
 
 /**
  * Implements the ISmsImplBase interface used in the SmsManager API.
@@ -56,7 +63,8 @@
 
     private final Context mContext;
 
-    protected SmsController(Context context) {
+    @VisibleForTesting
+    public SmsController(Context context) {
         mContext = context;
         ServiceRegisterer smsServiceRegisterer = TelephonyFrameworkInitializer
                 .getTelephonyServiceManager()
@@ -151,6 +159,23 @@
         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(), destAddr)) {
+            TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(mContext, subId,
+                    Binder.getCallingUid(), callingPackage);
+            sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_USER_NOT_ALLOWED);
+            return;
+        }
+
+        // Perform FDN check
+        if (isNumberBlockedByFDN(subId, destAddr, callingPackage)) {
+            sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE);
+            return;
+        }
+
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
             iccSmsIntMgr.sendData(callingPackage, callingAttributionTag, destAddr, scAddr, destPort,
@@ -186,14 +211,74 @@
             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);
             return;
         }
+
+        // Check if user is associated with the subscription
+        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 (!skipFdnCheck && isNumberBlockedByFDN(subId, destAddr, callingPackage)) {
+            sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE);
+            return;
+        }
+
         long token = Binder.clearCallingIdentity();
         SubscriptionInfo info;
         try {
@@ -201,11 +286,12 @@
         } finally {
             Binder.restoreCallingIdentity(token);
         }
+
         if (isBluetoothSubscription(info)) {
             sendBluetoothText(info, destAddr, text, sentIntent, deliveryIntent);
         } else {
             sendIccText(subId, callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
-                    persistMessageForNonDefaultSmsApp, messageId);
+                    persistMessageForNonDefaultSmsApp, messageId, skipShortCodeCheck);
         }
     }
 
@@ -222,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);
         }
     }
@@ -259,6 +346,23 @@
         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(), destAddr)) {
+            TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(mContext, subId,
+                    Binder.getCallingUid(), callingPackage);
+            sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_USER_NOT_ALLOWED);
+            return;
+        }
+
+        // Perform FDN check
+        if (isNumberBlockedByFDN(subId, destAddr, callingPackage)) {
+            sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE);
+            return;
+        }
+
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
             iccSmsIntMgr.sendTextWithOptions(callingPackage, callingAttributionTag, destAddr,
@@ -281,6 +385,23 @@
         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(), destAddr)) {
+            TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(mContext, subId,
+                    Binder.getCallingUid(), callingPackage);
+            sendErrorInPendingIntents(sentIntents, SmsManager.RESULT_USER_NOT_ALLOWED);
+            return;
+        }
+
+        // Perform FDN check
+        if (isNumberBlockedByFDN(subId, destAddr, callingPackage)) {
+            sendErrorInPendingIntents(sentIntents, SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE);
+            return;
+        }
+
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
             iccSmsIntMgr.sendMultipartText(callingPackage, callingAttributionTag, destAddr, scAddr,
@@ -288,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);
         }
     }
@@ -301,6 +422,23 @@
         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(), destAddr)) {
+            TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(mContext, subId,
+                    Binder.getCallingUid(), callingPackage);
+            sendErrorInPendingIntents(sentIntents, SmsManager.RESULT_USER_NOT_ALLOWED);
+            return;
+        }
+
+        // Perform FDN check
+        if (isNumberBlockedByFDN(subId, destAddr, callingPackage)) {
+            sendErrorInPendingIntents(sentIntents, SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE);
+            return;
+        }
+
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
             iccSmsIntMgr.sendMultipartTextWithOptions(callingPackage, callingAttributionTag,
@@ -409,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.");
@@ -481,14 +619,15 @@
     @Override
     public int getPreferredSmsSubscription() {
         // If there is a default, choose that one.
-        int defaultSubId = SubscriptionController.getInstance().getDefaultSmsSubId();
+        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 {
-            int[] activeSubs = SubscriptionController.getInstance()
+            int[] activeSubs = SubscriptionManagerService.getInstance()
                     .getActiveSubIdList(true /*visibleOnly*/);
             if (activeSubs.length == 1) {
                 return activeSubs[0];
@@ -519,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);
@@ -537,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);
@@ -690,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) {
@@ -714,6 +909,24 @@
     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.");
+            return;
+        }
+
+        // Check if user is associated with the subscription
+        if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(mContext, subId,
+                Binder.getCallingUserHandle(), number)) {
+            TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(mContext, subId,
+                    Binder.getCallingUid(), callingPackage);
+            sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_USER_NOT_ALLOWED);
+            return;
+        }
+
         if (port == 0) {
             sendTextForSubscriberWithSelfPermissionsInternal(subId, callingPackage,
                     callingAttributionTag, number, null, text, sentIntent, null, false,
@@ -856,4 +1069,49 @@
     public static String formatCrossStackMessageId(long id) {
         return "{x-message-id:" + id + "}";
     }
-}
+
+    /**
+     * The following function checks if destination address or smsc is blocked due to FDN.
+     * @param subId subscription ID
+     * @param destAddr destination address of the message
+     * @return true if either destAddr or smscAddr is blocked due to FDN.
+     */
+    @VisibleForTesting
+    public boolean isNumberBlockedByFDN(int subId, String destAddr, String callingPackage) {
+        int phoneId = SubscriptionManager.getPhoneId(subId);
+        if (!FdnUtils.isFdnEnabled(phoneId)) {
+            return false;
+        }
+
+        // Skip FDN check for emergency numbers
+        TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+        if (tm.isEmergencyNumber(destAddr)) {
+            return false;
+        }
+
+        // Check if destAddr is present in FDN list
+        String defaultCountryIso = tm.getSimCountryIso().toUpperCase(Locale.ENGLISH);
+        if (FdnUtils.isNumberBlockedByFDN(phoneId, destAddr, defaultCountryIso)) {
+            return true;
+        }
+
+        // Get SMSC address for this subscription
+        IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
+        String smscAddr;
+        if (iccSmsIntMgr != null) {
+            long identity = Binder.clearCallingIdentity();
+            try {
+                smscAddr =  iccSmsIntMgr.getSmscAddressFromIccEf(callingPackage);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        } else {
+            Rlog.e(LOG_TAG, "getSmscAddressFromIccEfForSubscriber iccSmsIntMgr is null"
+                    + " for Subscription: " + subId);
+            return true;
+        }
+
+        // Check if smscAddr is present in FDN list
+        return FdnUtils.isNumberBlockedByFDN(phoneId, smscAddr, defaultCountryIso);
+    }
+}
\ No newline at end of file
diff --git a/src/java/com/android/internal/telephony/SmsDispatchersController.java b/src/java/com/android/internal/telephony/SmsDispatchersController.java
index 53556ac..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 =
@@ -503,7 +778,8 @@
                             scAddr, destAddr, text, (tracker.mDeliveryIntent != null), null);
                 } else {
                     pdu = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(
-                            scAddr, destAddr, text, (tracker.mDeliveryIntent != null), null);
+                            scAddr, destAddr, text, (tracker.mDeliveryIntent != null), null,
+                            0, 0, 0, -1, tracker.mMessageRef);
                 }
             } else if (map.containsKey("data")) {
                 byte[] data = (byte[]) map.get("data");
@@ -518,7 +794,7 @@
                 } else {
                     pdu = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(
                             scAddr, destAddr, destPort.intValue(), data,
-                            (tracker.mDeliveryIntent != null));
+                            (tracker.mDeliveryIntent != null), tracker.mMessageRef);
                 }
             }
 
@@ -544,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
@@ -589,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.
      *
@@ -670,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);
@@ -783,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);
             }
         }
     }
@@ -909,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,
@@ -1065,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 b890a4d..2736f7a
--- a/src/java/com/android/internal/telephony/SmsStorageMonitor.java
+++ b/src/java/com/android/internal/telephony/SmsStorageMonitor.java
@@ -18,17 +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;
 
 /**
@@ -39,16 +44,28 @@
  * 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;
+
+    /** Delay before next attempt on a failed memory status reporting, in milliseconds. */
+    private static final int RETRY_DELAY = 5000; // 5 seconds
 
     /** SIM/RUIM storage is full */
     private static final int EVENT_ICC_FULL = 1;
 
+    /** Report memory status */
+    private static final int EVENT_REPORT_MEMORY_STATUS = 2;
+
     /** Memory status reporting is acknowledged by RIL */
-    private static final int EVENT_REPORT_MEMORY_STATUS_DONE = 2;
+    private static final int EVENT_REPORT_MEMORY_STATUS_DONE = 3;
+
+    /** Retry memory status reporting */
+    private static final int EVENT_RETRY_MEMORY_STATUS_REPORTING = 4;
 
     /** Radio is ON */
-    private static final int EVENT_RADIO_ON = 3;
+    private static final int EVENT_RADIO_ON = 5;
 
     /** Context from phone object passed to constructor. */
     private final Context mContext;
@@ -56,14 +73,23 @@
     /** Wake lock to ensure device stays awake while dispatching the SMS intent. */
     private PowerManager.WakeLock mWakeLock;
 
-    private boolean mReportMemoryStatusPending;
+    private int mMaxRetryCount = MAX_RETRIES;
+    private int mRetryDelay = RETRY_DELAY;
+    private int mRetryCount = 0;
+    private boolean mIsWaitingResponse = false;
+    private boolean mNeedNewReporting = false;
+    private boolean mIsMemoryStatusReportingFailed = false;
 
     /** it is use to put in to extra value for SIM_FULL_ACTION and SMS_REJECTED_ACTION */
     Phone mPhone;
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    final CommandsInterface mCi;                            // accessed from inner class
-    boolean mStorageAvailable = true;                       // accessed from inner class
+    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
@@ -93,6 +119,41 @@
         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;
+    }
+
+    @VisibleForTesting
+    public void setRetryDelayInMillis(int delay) {
+        mRetryDelay = delay;
+    }
+
     public void dispose() {
         mCi.unSetOnIccSmsFull(this);
         mCi.unregisterForOn(this);
@@ -105,35 +166,97 @@
      */
     @Override
     public void handleMessage(Message msg) {
-        AsyncResult ar;
+        boolean isAvailable = mStorageAvailable;
 
         switch (msg.what) {
             case EVENT_ICC_FULL:
                 handleIccFull();
                 break;
 
-            case EVENT_REPORT_MEMORY_STATUS_DONE:
-                ar = (AsyncResult) msg.obj;
-                if (ar.exception != null) {
-                    mReportMemoryStatusPending = true;
-                    Rlog.v(TAG, "Memory status report to modem pending : mStorageAvailable = "
-                            + mStorageAvailable);
+            case EVENT_REPORT_MEMORY_STATUS:
+                if (mIsWaitingResponse) {
+                    Rlog.v(TAG, "EVENT_REPORT_MEMORY_STATUS - deferred");
+                    // Previous reporting is on-going now. New reporting will be issued in
+                    // EVENT_REPORT_MEMORY_STATUS_DONE.
+                    mNeedNewReporting = true;
                 } else {
-                    mReportMemoryStatusPending = false;
+                    Rlog.v(TAG, "EVENT_REPORT_MEMORY_STATUS - report sms memory status"
+                            + (isAvailable ? "(not full)" : "(full)"));
+                    // Clear a delayed EVENT_RETRY_MEMORY_STATUS_REPORTING
+                    // and mIsMemoryStatusReportingFailed.
+                    removeMessages(EVENT_RETRY_MEMORY_STATUS_REPORTING);
+                    mIsMemoryStatusReportingFailed = false;
+                    mRetryCount = 0;
+                    sendMemoryStatusReport(isAvailable);
                 }
                 break;
 
+            case EVENT_REPORT_MEMORY_STATUS_DONE:
+                AsyncResult ar = (AsyncResult) msg.obj;
+                mIsWaitingResponse = false;
+                Rlog.v(TAG, "EVENT_REPORT_MEMORY_STATUS_DONE - "
+                        + (ar.exception == null ? "succeeded" : "failed"));
+                if (mNeedNewReporting) {
+                    Rlog.v(TAG, "EVENT_REPORT_MEMORY_STATUS_DONE - report again now"
+                            + (isAvailable ? "(not full)" : "(full)"));
+                    // New reportings have been requested, report last memory status here.
+                    mNeedNewReporting = false;
+                    mRetryCount = 0;
+                    sendMemoryStatusReport(isAvailable);
+                } else {
+                    if (ar.exception != null) {
+                        if (mRetryCount++ < mMaxRetryCount) {
+                            Rlog.v(TAG, "EVENT_REPORT_MEMORY_STATUS_DONE - retry in "
+                                    + mRetryDelay + "ms");
+                            sendMessageDelayed(
+                                    obtainMessage(EVENT_RETRY_MEMORY_STATUS_REPORTING),
+                                    mRetryDelay);
+                        } else {
+                            Rlog.v(TAG, "EVENT_REPORT_MEMORY_STATUS_DONE - "
+                                    + "no retry anymore(pended)");
+                            mRetryCount = 0;
+                            mIsMemoryStatusReportingFailed = true;
+                        }
+                    } else {
+                        mRetryCount = 0;
+                        mIsMemoryStatusReportingFailed = false;
+                    }
+                }
+                break;
+
+            case EVENT_RETRY_MEMORY_STATUS_REPORTING:
+                Rlog.v(TAG, "EVENT_RETRY_MEMORY_STATUS_REPORTING - retry"
+                        + (isAvailable ? "(not full)" : "(full)"));
+                sendMemoryStatusReport(isAvailable);
+                break;
+
             case EVENT_RADIO_ON:
-                if (mReportMemoryStatusPending) {
-                    Rlog.v(TAG, "Sending pending memory status report : mStorageAvailable = "
-                            + mStorageAvailable);
-                    mCi.reportSmsMemoryStatus(mStorageAvailable,
-                            obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE));
+                if (mIsMemoryStatusReportingFailed) {
+                    Rlog.v(TAG, "EVENT_RADIO_ON - report failed sms memory status"
+                            + (isAvailable ? "(not full)" : "(full)"));
+                    sendMemoryStatusReport(isAvailable);
                 }
                 break;
         }
     }
 
+    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));
+    }
+
     private void createWakelock() {
         PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SmsStorageMonitor");
@@ -145,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);
@@ -161,12 +289,12 @@
     private final BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_FULL)) {
-                mStorageAvailable = false;
-                mCi.reportSmsMemoryStatus(false, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE));
-            } else if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_NOT_FULL)) {
-                mStorageAvailable = true;
-                mCi.reportSmsMemoryStatus(true, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE));
+            final String action = intent.getAction();
+            if (Intent.ACTION_DEVICE_STORAGE_FULL.equals(action)
+                    || Intent.ACTION_DEVICE_STORAGE_NOT_FULL.equals(action)) {
+                mStorageAvailable =
+                        Intent.ACTION_DEVICE_STORAGE_FULL.equals(action) ? false : true;
+                sendMessage(obtainMessage(EVENT_REPORT_MEMORY_STATUS));
             }
         }
     };
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 7e762d7..0000000
--- a/src/java/com/android/internal/telephony/SubscriptionController.java
+++ /dev/null
@@ -1,4820 +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.app.AppOpsManager;
-import android.app.PendingIntent;
-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.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.IccCardConstants.State;
-import com.android.internal.telephony.data.DataEnabledOverride;
-import com.android.internal.telephony.data.PhoneSwitcher;
-import com.android.internal.telephony.metrics.TelephonyMetrics;
-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;
-
-    private AppOpsManager mAppOps;
-
-    // Allows test mocks to avoid SELinux failures on invalidate calls.
-    private static boolean sCachingEnabled = true;
-
-    // Each slot can have multiple subs.
-    private static class WatchedSlotIndexToSubIds {
-        private Map<Integer, ArrayList<Integer>> mSlotIndexToSubIds = new ConcurrentHashMap<>();
-
-        WatchedSlotIndexToSubIds() {
-        }
-
-        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<Integer>(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 static WatchedSlotIndexToSubIds sSlotIndexToSubIds = new WatchedSlotIndexToSubIds();
-
-    protected static WatchedInt sDefaultFallbackSubId =
-            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.DATA_ENABLED_OVERRIDE_RULES,
-            SubscriptionManager.UICC_APPLICATIONS_ENABLED,
-            SubscriptionManager.IMS_RCS_UCE_ENABLED,
-            SubscriptionManager.CROSS_SIM_CALLING_ENABLED,
-            SubscriptionManager.NR_ADVANCED_CALLING_ENABLED
-    ));
-
-    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 (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();
-                                }
-                            }
-                        }
-                    }
-                });
-
-        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);
-    }
-
-    /**
-     * 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;
-        }
-    }
-
-    /**
-     * Broadcast when SubscriptionInfo has changed
-     * FIXME: Hopefully removed if the API council accepts SubscriptionInfoListener
-     */
-     private void broadcastSimInfoContentChanged() {
-        Intent intent = new Intent(TelephonyIntents.ACTION_SUBINFO_CONTENT_CHANGE);
-        mContext.sendBroadcast(intent);
-        intent = new Intent(TelephonyIntents.ACTION_SUBINFO_RECORD_UPDATED);
-        mContext.sendBroadcast(intent);
-     }
-
-    /**
-     * 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();
-
-        // FIXME: Remove if listener technique accepted.
-        broadcastSimInfoContentChanged();
-
-        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
-     * @return the query result of desired SubInfoRecord
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private SubscriptionInfo getSubInfoRecord(Cursor cursor) {
-        int id = cursor.getInt(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID));
-        String iccId = cursor.getString(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.ICC_ID));
-        int simSlotIndex = cursor.getInt(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.SIM_SLOT_INDEX));
-        String displayName = cursor.getString(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.DISPLAY_NAME));
-        String carrierName = cursor.getString(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.CARRIER_NAME));
-        int nameSource = cursor.getInt(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.NAME_SOURCE));
-        int iconTint = cursor.getInt(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.HUE));
-        String number = cursor.getString(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.NUMBER));
-        int dataRoaming = cursor.getInt(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.DATA_ROAMING));
-        String mcc = cursor.getString(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.MCC_STRING));
-        String mnc = 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(",");
-
-        // cardId is the private ICCID/EID string, also known as the card string
-        String cardId = cursor.getString(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.CARD_ID));
-        String countryIso = cursor.getString(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.ISO_COUNTRY_CODE));
-        // publicCardId is the publicly exposed int card ID
-        int publicCardId = mUiccController.convertToPublicCardId(cardId);
-        boolean isEmbedded = cursor.getInt(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.IS_EMBEDDED)) == 1;
-        int carrierId = cursor.getInt(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.CARRIER_ID));
-        UiccAccessRule[] accessRules;
-        if (isEmbedded) {
-            accessRules = UiccAccessRule.decodeRules(cursor.getBlob(
-                    cursor.getColumnIndexOrThrow(SubscriptionManager.ACCESS_RULES)));
-        } else {
-            accessRules = null;
-        }
-        UiccAccessRule[] carrierConfigAccessRules = UiccAccessRule.decodeRules(cursor.getBlob(
-            cursor.getColumnIndexOrThrow(SubscriptionManager.ACCESS_RULES_FROM_CARRIER_CONFIGS)));
-        boolean isOpportunistic = cursor.getInt(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.IS_OPPORTUNISTIC)) == 1;
-        String groupUUID = cursor.getString(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.GROUP_UUID));
-        int profileClass = cursor.getInt(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.PROFILE_CLASS));
-        int portIndex = cursor.getInt(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.PORT_INDEX));
-        int subType = cursor.getInt(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.SUBSCRIPTION_TYPE));
-        String groupOwner = getOptionalStringFromCursor(cursor, SubscriptionManager.GROUP_OWNER,
-                /*defaultVal*/ null);
-        boolean areUiccApplicationsEnabled = cursor.getInt(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.UICC_APPLICATIONS_ENABLED)) == 1;
-
-        int usageSetting = cursor.getInt(cursor.getColumnIndexOrThrow(
-                SubscriptionManager.USAGE_SETTING));
-
-        if (VDBG) {
-            String iccIdToPrint = SubscriptionInfo.givePrintableIccid(iccId);
-            String cardIdToPrint = SubscriptionInfo.givePrintableIccid(cardId);
-            logd("[getSubInfoRecord] id:" + id + " iccid:" + iccIdToPrint + " simSlotIndex:"
-                    + simSlotIndex + " carrierid:" + carrierId + " displayName:" + displayName
-                    + " nameSource:" + nameSource + " iconTint:" + iconTint
-                    + " dataRoaming:" + dataRoaming + " mcc:" + mcc + " mnc:" + mnc
-                    + " countIso:" + countryIso + " isEmbedded:"
-                    + isEmbedded + " accessRules:" + Arrays.toString(accessRules)
-                    + " carrierConfigAccessRules: " + Arrays.toString(carrierConfigAccessRules)
-                    + " cardId:" + cardIdToPrint + " portIndex:" + portIndex
-                    + " publicCardId:" + publicCardId
-                    + " isOpportunistic:" + isOpportunistic + " groupUUID:" + groupUUID
-                    + " profileClass:" + profileClass + " subscriptionType: " + subType
-                    + " carrierConfigAccessRules:" + carrierConfigAccessRules
-                    + " areUiccApplicationsEnabled: " + areUiccApplicationsEnabled
-                    + " usageSetting: " + usageSetting);
-        }
-
-        // If line1number has been set to a different number, use it instead.
-        String line1Number = mTelephonyManager.getLine1Number(id);
-        if (!TextUtils.isEmpty(line1Number) && !line1Number.equals(number)) {
-            number = line1Number;
-        }
-        // 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?
-        SubscriptionInfo info = new SubscriptionInfo(id, iccId, simSlotIndex, displayName,
-                carrierName, nameSource, iconTint, number, dataRoaming, /* icon= */ null,
-                mcc, mnc, countryIso, isEmbedded, accessRules, cardId, publicCardId,
-                isOpportunistic, groupUUID, /* isGroupDisabled= */ false , carrierId, profileClass,
-                subType, groupOwner, carrierConfigAccessRules, areUiccApplicationsEnabled,
-                portIndex, usageSetting);
-        info.setAssociatedPlmns(ehplmns, hplmns);
-        return info;
-    }
-
-    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
-     */
-    @Override
-    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 SubInfoRecord to subinfo database if needed
-     * @param iccId the IccId of the SIM card
-     * @param slotIndex the slot which the SIM is inserted
-     * @return 0 if success, < 0 on error.
-     */
-    @Override
-    public int addSubInfoRecord(String iccId, int slotIndex) {
-        return addSubInfo(iccId, null, slotIndex, SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
-    }
-
-    /**
-     * 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]"
-                                        + " sSlotIndexToSubIds.size=" + sSlotIndexToSubIds.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) {
-                                    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 = getSubIdUsingPhoneId(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=" + sSlotIndexToSubIds.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 = sSlotIndexToSubIds.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();
-
-        sSlotIndexToSubIds.remove(slotIndex);
-    }
-
-    /**
-     * 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 = getSubIdUsingPhoneId(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 tint the tint color of the SIM
-     * @param subId the unique SubInfoRecord index in database
-     * @return the number of records updated
-     */
-    @Override
-    public int setIconTint(int tint, int subId) {
-        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.getNameSource()) {
-            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.getNameSource();
-                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:
-                values.put(propKey, cursor.getInt(columnIndex));
-                break;
-            case SubscriptionManager.DISPLAY_NAME:
-            case SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES:
-                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.
-     * @return the number of records updated
-     */
-    public int 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);
-
-            int result = mContext.getContentResolver().update(
-                    SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
-
-            // Refresh the Cache of Active Subscription Info List
-            refreshCachedActiveSubscriptionInfoList();
-
-            notifyUiccAppsEnableChanged();
-            notifySubscriptionInfoChanged();
-
-            return result;
-        } 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 = sSlotIndexToSubIds.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 : sSlotIndexToSubIds.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 subId for specified slot Id.
-     * @deprecated
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @Override
-    @Deprecated
-    public int[] getSubId(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 = sSlotIndexToSubIds.size();
-        if (size == 0) {
-            if (VDBG) {
-                logd("[getSubId]- sSlotIndexToSubIds.size == 0, return null slotIndex="
-                        + slotIndex);
-            }
-            return null;
-        }
-
-        // Convert ArrayList to array
-        ArrayList<Integer> subIds = sSlotIndexToSubIds.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=" + 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 = sSlotIndexToSubIds.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: sSlotIndexToSubIds.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
-     */
-    @Override
-    public int clearSubInfo() {
-        enforceModifyPhoneState("clearSubInfo");
-
-        // Now that all security checks passes, perform the operation as ourselves.
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            int size = sSlotIndexToSubIds.size();
-
-            if (size == 0) {
-                if (DBG) logdl("[clearSubInfo]- no simInfo size=" + size);
-                return 0;
-            }
-
-            sSlotIndexToSubIds.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 = sDefaultFallbackSubId.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);
-        PhoneAccountHandle currentHandle = telecomManager.getUserSelectedOutgoingPhoneAccount();
-        logd("[setDefaultVoiceSubId] current phoneAccountHandle=" + currentHandle);
-
-        if (!Objects.equals(currentHandle, newHandle)) {
-            telecomManager.setUserSelectedOutgoingPhoneAccount(newHandle);
-            logd("[setDefaultVoiceSubId] change to phoneAccountHandle=" + newHandle);
-        } else {
-            logd("[setDefaultVoiceSubId] default phoneAccountHandle not changed.");
-        }
-
-        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)) {
-            sDefaultFallbackSubId.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);
-                sDefaultFallbackSubId.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.
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public int getSubIdUsingPhoneId(int phoneId) {
-        int[] subIds = getSubId(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<>(sSlotIndexToSubIds.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;
-    }
-
-    /**
-     * Get the SIM state for the slot index.
-     * For Remote-SIMs, this method returns {@link #IccCardConstants.State.UNKNOWN}
-     * @return SIM state as the ordinal of {@See IccCardConstants.State}
-     */
-    @Override
-    public int getSimStateForSlotIndex(int slotIndex) {
-        State simState;
-        String err;
-        if (slotIndex < 0) {
-            simState = IccCardConstants.State.UNKNOWN;
-            err = "invalid slotIndex";
-        } else {
-            Phone phone = null;
-            try {
-                phone = PhoneFactory.getPhone(slotIndex);
-            } catch (IllegalStateException e) {
-                // ignore
-            }
-            if (phone == null) {
-                simState = IccCardConstants.State.UNKNOWN;
-                err = "phone == null";
-            } else {
-                IccCard icc = phone.getIccCard();
-                if (icc == null) {
-                    simState = IccCardConstants.State.UNKNOWN;
-                    err = "icc == null";
-                } else {
-                    simState = icc.getState();
-                    err = "";
-                }
-            }
-        }
-        if (VDBG) {
-            logd("getSimStateForSlotIndex: " + err + " simState=" + simState
-                    + " ordinal=" + simState.ordinal() + " slotIndex=" + slotIndex);
-        }
-        return simState.ordinal();
-    }
-
-    /**
-     * 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:
-                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.DATA_ENABLED_OVERRIDE_RULES:
-                        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:
-                            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(" defaultDataPhoneId=" + SubscriptionManager
-                    .from(mContext).getDefaultDataPhoneId());
-            pw.println(" defaultVoicePhoneId=" + SubscriptionManager.getDefaultVoicePhoneId());
-            pw.println(" defaultSmsPhoneId=" + SubscriptionManager
-                    .from(mContext).getDefaultSmsPhoneId());
-            pw.flush();
-
-            for (Entry<Integer, ArrayList<Integer>> entry : sSlotIndexToSubIds.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 = getSubInfo(
-                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + subId, null);
-
-        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("a3ab0b9d-f2aa-4baf-911d-7096c0d4645a"),
-                        "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("a3ab0b9d-f2aa-4baf-911d-7096c0d4645a"),
-                        "Get preferred data sub: phoneSwitcher is null.");
-                return SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
-            }
-
-            return phoneSwitcher.getOpportunisticDataSubscriptionId();
-        } 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 " + 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 will either have {@link android.Manifest.permission#READ_PHONE_STATE}
-     * permission or had carrier privilege permission on the subscription.
-     * {@link TelephonyManager#hasCarrierPrivileges(int)}
-     *
-     * @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);
-        }
-
-        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 = getSubIdUsingPhoneId(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.
-     */
-    @Override
-    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) 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 = getSubIdUsingPhoneId(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 result = new SubscriptionInfo(subInfo);
-        if (!hasIdentifierAccess) {
-            result.clearIccId();
-            result.clearCardString();
-            result.clearGroupUuid();
-        }
-        if (!hasPhoneNumberAccess) {
-            result.clearNumber();
-        }
-        return result;
-    }
-
-    private synchronized boolean addToSubIdList(int slotIndex, int subId, int subscriptionType) {
-        ArrayList<Integer> subIdsList = sSlotIndexToSubIds.getCopy(slotIndex);
-        if (subIdsList == null) {
-            subIdsList = new ArrayList<>();
-            sSlotIndexToSubIds.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.
-            sSlotIndexToSubIds.addToSubIdList(slotIndex, subId);
-        } else {
-            // for all other types of subscriptions, a slot can have only one subscription at a time
-            sSlotIndexToSubIds.clearSubIdList(slotIndex);
-            sSlotIndexToSubIds.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 : sSlotIndexToSubIds.entrySet()) {
-            if (entry.getKey() != slotIndex && entry.getValue() != null
-                    && entry.getValue().contains(subId)) {
-                logdl("addToSubIdList - remove " + entry.getKey());
-                sSlotIndexToSubIds.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 sSlotIndexToSubIds.getMap();
-    }
-
-    /**
-     * This is only for testing
-     * @hide
-     */
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    public void resetStaticMembers() {
-        sDefaultFallbackSubId.set(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-        mDefaultPhoneId = SubscriptionManager.DEFAULT_PHONE_INDEX;
-    }
-
-    private void notifyOpportunisticSubscriptionInfoChanged() {
-        TelephonyRegistryManager trm =
-                (TelephonyRegistryManager)
-                        mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
-        if (DBG) logd("notifyOpptSubscriptionInfoChanged:");
-        trm.notifyOpportunisticSubscriptionInfoChanged();
-    }
-
-    private void refreshCachedOpportunisticSubscriptionInfoList() {
-        List<SubscriptionInfo> subList = getSubInfo(
-                SubscriptionManager.IS_OPPORTUNISTIC + "=1 AND ("
-                        + SubscriptionManager.SIM_SLOT_INDEX + ">=0 OR "
-                        + SubscriptionManager.IS_EMBEDDED + "=1)", null);
-        synchronized (mSubInfoListLock) {
-            List<SubscriptionInfo> oldOpptCachedList = mCacheOpportunisticSubInfoList;
-
-            if (subList != null) {
-                subList.sort(SUBSCRIPTION_INFO_COMPARATOR);
-            } else {
-                subList = new ArrayList<>();
-            }
-
-            mCacheOpportunisticSubInfoList = subList;
-
-            for (SubscriptionInfo info : mCacheOpportunisticSubInfoList) {
-                if (shouldDisableSubGroup(info.getGroupUuid())) {
-                    info.setGroupDisabled(true);
-                }
-            }
-
-            if (DBG_CACHE) {
-                if (!mCacheOpportunisticSubInfoList.isEmpty()) {
-                    for (SubscriptionInfo si : mCacheOpportunisticSubInfoList) {
-                        logd("[refreshCachedOpptSubscriptionInfoList] Setting Cached info="
-                                + si);
-                    }
-                } else {
-                    logdl("[refreshCachedOpptSubscriptionInfoList]- 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 allowing mobile data during voice call.
-     *
-     * @param subId Subscription index
-     * @param rules Data enabled override rules in string format. See {@link DataEnabledOverride}
-     * for details.
-     * @return {@code true} if settings changed, otherwise {@code false}.
-     */
-    public boolean setDataEnabledOverrideRules(int subId, @NonNull String rules) {
-        if (DBG) logd("[setDataEnabledOverrideRules]+ rules:" + rules + " subId:" + subId);
-
-        validateSubId(subId);
-        ContentValues value = new ContentValues(1);
-        value.put(SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES, rules);
-
-        boolean result = updateDatabase(value, subId, true) > 0;
-
-        if (result) {
-            // Refresh the Cache of Active Subscription Info List
-            refreshCachedActiveSubscriptionInfoList();
-            notifySubscriptionInfoChanged();
-        }
-
-        return result;
-    }
-
-    /**
-     * Get data enabled override rules.
-     *
-     * @param subId Subscription index
-     * @return Data enabled override rules in string
-     */
-    @NonNull
-    public String getDataEnabledOverrideRules(int subId) {
-        return TelephonyUtils.emptyIfNull(getSubscriptionProperty(subId,
-                SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES));
-    }
-
-    /**
-     * 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;
-    }
-
-    /**
-     * @hide
-     */
-    private void setGlobalSetting(String name, int value) {
-        Settings.Global.putInt(mContext.getContentResolver(), name, value);
-        if (name == Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION) {
-            invalidateDefaultDataSubIdCaches();
-            invalidateActiveDataSubIdCaches();
-            invalidateDefaultSubIdCaches();
-            invalidateSlotIndexCaches();
-        } else if (name == Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION) {
-            invalidateDefaultSubIdCaches();
-            invalidateSlotIndexCaches();
-        } else if (name == Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION) {
-            invalidateDefaultSmsSubIdCaches();
-        }
-    }
-
-    /**
-     * @hide
-     */
-    private static void invalidateDefaultSubIdCaches() {
-        if (sCachingEnabled) {
-            SubscriptionManager.invalidateDefaultSubIdCaches();
-        }
-    }
-
-    /**
-     * @hide
-     */
-    private static void invalidateDefaultDataSubIdCaches() {
-        if (sCachingEnabled) {
-            SubscriptionManager.invalidateDefaultDataSubIdCaches();
-        }
-    }
-
-    /**
-     * @hide
-     */
-    private static void invalidateDefaultSmsSubIdCaches() {
-        if (sCachingEnabled) {
-            SubscriptionManager.invalidateDefaultSmsSubIdCaches();
-        }
-    }
-
-    /**
-     * @hide
-     */
-    public static void invalidateActiveDataSubIdCaches() {
-        if (sCachingEnabled) {
-            SubscriptionManager.invalidateActiveDataSubIdCaches();
-        }
-    }
-
-    /**
-     * @hide
-     */
-    protected static void invalidateSlotIndexCaches() {
-        if (sCachingEnabled) {
-            SubscriptionManager.invalidateSlotIndexCaches();
-        }
-    }
-
-    /**
-     * @hide
-     */
-    @VisibleForTesting
-    public static void disableCaching() {
-        sCachingEnabled = false;
-    }
-
-    /**
-     * @hide
-     */
-    @VisibleForTesting
-    public static void enableCaching() {
-        sCachingEnabled = true;
-    }
-}
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 3ab229a..0000000
--- a/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
+++ /dev/null
@@ -1,1392 +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.TelephonyManager.SimState;
-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. ACTION_SUBINFO_CONTENT_CHANGE happens 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) {
-        SubscriptionManager subManager = SubscriptionManager.from(sContext);
-        subManager.restoreSimSpecificSettingsForIccIdFromBackup(sIccId[phoneId]);
-    }
-
-    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).getNameSource();
-                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.getSubIdUsingPhoneId(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, false);
-            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 " + simStateString(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 " + simStateString(state)
-                    + " for phone: " + phoneId + " slot: " + slotId + "port: "
-                    + slot.getPortIndexFromPhoneId(phoneId));
-            sContext.sendBroadcast(i, Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
-            TelephonyMetrics.getInstance().updateSimState(phoneId, state);
-        }
-    }
-
-    /**
-     * Convert SIM state into string
-     *
-     * @param state SIM state
-     * @return SIM state in string format
-     */
-    public static String simStateString(@SimState int state) {
-        switch (state) {
-            case TelephonyManager.SIM_STATE_UNKNOWN:
-                return "UNKNOWN";
-            case TelephonyManager.SIM_STATE_ABSENT:
-                return "ABSENT";
-            case TelephonyManager.SIM_STATE_PIN_REQUIRED:
-                return "PIN_REQUIRED";
-            case TelephonyManager.SIM_STATE_PUK_REQUIRED:
-                return "PUK_REQUIRED";
-            case TelephonyManager.SIM_STATE_NETWORK_LOCKED:
-                return "NETWORK_LOCKED";
-            case TelephonyManager.SIM_STATE_READY:
-                return "READY";
-            case TelephonyManager.SIM_STATE_NOT_READY:
-                return "NOT_READY";
-            case TelephonyManager.SIM_STATE_PERM_DISABLED:
-                return "PERM_DISABLED";
-            case TelephonyManager.SIM_STATE_CARD_IO_ERROR:
-                return "CARD_IO_ERROR";
-            case TelephonyManager.SIM_STATE_CARD_RESTRICTED:
-                return "CARD_RESTRICTED";
-            case TelephonyManager.SIM_STATE_LOADED:
-                return "LOADED";
-            case TelephonyManager.SIM_STATE_PRESENT:
-                return "PRESENT";
-            default:
-                return "INVALID";
-        }
-    }
-
-    @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 dcbd2d5..f7f24a2 100644
--- a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
+++ b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
@@ -27,7 +27,6 @@
 import android.system.Os;
 import android.system.OsConstants;
 import android.system.StructStatVfs;
-import android.telephony.AccessNetworkConstants.TransportType;
 import android.text.TextUtils;
 
 import com.android.ims.ImsManager;
@@ -40,11 +39,9 @@
 import com.android.internal.telephony.data.DataSettingsManager;
 import com.android.internal.telephony.data.LinkBandwidthEstimator;
 import com.android.internal.telephony.data.PhoneSwitcher;
-import com.android.internal.telephony.dataconnection.DataEnabledSettings;
-import com.android.internal.telephony.dataconnection.DcTracker;
-import com.android.internal.telephony.dataconnection.TransportManager;
 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;
@@ -313,10 +310,6 @@
         return new SimActivationTracker(phone);
     }
 
-    public DcTracker makeDcTracker(Phone phone, @TransportType int transportType) {
-        return new DcTracker(phone, transportType);
-    }
-
     public CarrierSignalAgent makeCarrierSignalAgent(Phone phone) {
         return new CarrierSignalAgent(phone);
     }
@@ -394,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) {
@@ -404,10 +405,6 @@
         return new DeviceStateMonitor(phone);
     }
 
-    public TransportManager makeTransportManager(Phone phone) {
-        return new TransportManager(phone);
-    }
-
     /**
      * Make access networks manager
      *
@@ -430,10 +427,6 @@
         return new LocaleTracker(phone, nitzStateMachine, looper);
     }
 
-    public DataEnabledSettings makeDataEnabledSettings(Phone phone) {
-        return new DataEnabledSettings(phone);
-    }
-
     public Phone makePhone(Context context, CommandsInterface ci, PhoneNotifier notifier,
             int phoneId, int precisePhoneType,
             TelephonyComponentFactory telephonyComponentFactory) {
@@ -441,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);
@@ -457,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);
     }
 
     /**
@@ -469,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 40bb212..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;
@@ -62,7 +63,6 @@
  *
  */
 public class TelephonyTester {
-    private static final String LOG_TAG = "TelephonyTester";
     private static final boolean DBG = true;
 
     /**
@@ -144,6 +144,7 @@
             "com.android.internal.telephony.TestServiceState";
 
     private static final String EXTRA_ACTION = "action";
+    private static final String EXTRA_PHONE_ID = "phone_id";
     private static final String EXTRA_VOICE_RAT = "voice_rat";
     private static final String EXTRA_DATA_RAT = "data_rat";
     private static final String EXTRA_VOICE_REG_STATE = "voice_reg_state";
@@ -157,6 +158,8 @@
 
     private static final String ACTION_RESET = "reset";
 
+    private String mLogTag;
+
     private static List<ImsExternalCallState> mImsExternalCallStates = null;
 
     private Intent mServiceStateTestIntent;
@@ -198,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();
@@ -216,7 +215,7 @@
                     if (DBG) log("onReceive: unknown action=" + action);
                 }
             } catch (BadParcelableException e) {
-                Rlog.w(LOG_TAG, e);
+                Rlog.w(mLogTag, e);
             }
         }
     };
@@ -225,6 +224,7 @@
         mPhone = phone;
 
         if (TelephonyUtils.IS_DEBUGGABLE) {
+            mLogTag = "TelephonyTester-" + mPhone.getPhoneId();
             IntentFilter filter = new IntentFilter();
 
             filter.addAction(mPhone.getActionDetached());
@@ -261,8 +261,8 @@
         }
     }
 
-    private static void log(String s) {
-        Rlog.d(LOG_TAG, s);
+    private void log(String s) {
+        Rlog.d(mLogTag, s);
     }
 
     private void handleSuppServiceFailedIntent(Intent intent) {
@@ -385,8 +385,25 @@
         }
     }
 
+    /**
+     * 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(
+                EXTRA_PHONE_ID, mPhone.getPhoneId())) {
+            return;
+        }
         if (mServiceStateTestIntent.hasExtra(EXTRA_ACTION)
                 && ACTION_RESET.equals(mServiceStateTestIntent.getStringExtra(EXTRA_ACTION))) {
             log("Service state override reset");
@@ -394,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 5b55c35..96fd208 100644
--- a/src/java/com/android/internal/telephony/UiccPhoneBookController.java
+++ b/src/java/com/android/internal/telephony/UiccPhoneBookController.java
@@ -19,11 +19,12 @@
 package com.android.internal.telephony;
 
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ContentValues;
 import android.os.Build;
 import android.os.TelephonyServiceManager.ServiceRegisterer;
 import android.telephony.TelephonyFrameworkInitializer;
-import android.content.ContentValues;
 
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.uicc.AdnCapacity;
 import com.android.internal.telephony.uicc.AdnRecord;
 import com.android.telephony.Rlog;
@@ -146,7 +147,7 @@
     private IccPhoneBookInterfaceManager
             getIccPhoneBookInterfaceManager(int subId) {
 
-        int 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 5bbe8b4..e374811 100644
--- a/src/java/com/android/internal/telephony/VisualVoicemailSmsFilter.java
+++ b/src/java/com/android/internal/telephony/VisualVoicemailSmsFilter.java
@@ -15,10 +15,13 @@
  */
 package com.android.internal.telephony;
 
+import static com.android.internal.telephony.SmsConstants.ENCODING_8BIT;
+
 import android.annotation.Nullable;
 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;
@@ -58,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";
@@ -75,7 +78,7 @@
             new PhoneAccountHandleConverter() {
 
                 @Override
-                public PhoneAccountHandle fromSubId(int subId) {
+                public PhoneAccountHandle fromSubId(int subId, Context context) {
                     if (!SubscriptionManager.isValidSubscriptionId(subId)) {
                         return null;
                     }
@@ -83,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()));
                 }
@@ -136,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");
@@ -294,7 +307,19 @@
                 result.firstMessage = message;
             }
             String body = message.getMessageBody();
-            if (body == null && message.getUserData() != null) {
+
+            /*
+             * For visual voice mail SMS message, UTF-8 is used by default
+             * {@link com.android.internal.telephony.SmsController#sendVisualVoicemailSmsForSubscriber}
+             *
+             * If config_sms_decode_gsm_8bit_data is enabled, GSM-8bit will be used to decode the
+             * received message. However, the message is most likely encoded with UTF-8. Therefore,
+             * we need to retry decoding the received message with UTF-8.
+             */
+            if ((body == null || (message.is3gpp()
+                    && message.getReceivedEncodingType() == ENCODING_8BIT))
+                    && message.getUserData() != null) {
+                Log.d(TAG, "getFullMessage decode using UTF-8");
                 // Attempt to interpret the user data as UTF-8. UTF-8 string over data SMS using
                 // 8BIT data coding scheme is our recommended way to send VVM SMS and is used in CTS
                 // Tests. The OMTP visual voicemail specification does not specify the SMS type and
@@ -303,7 +328,8 @@
                 try {
                     body = decoder.decode(byteBuffer).toString();
                 } catch (CharacterCodingException e) {
-                    // User data is not decode-able as UTF-8. Ignoring.
+                    Log.e(TAG, "getFullMessage: got CharacterCodingException"
+                            + " when decoding with UTF-8, e = " + e);
                     return null;
                 }
             }
diff --git a/src/java/com/android/internal/telephony/VoiceIndication.java b/src/java/com/android/internal/telephony/VoiceIndication.java
index 4ed82a6..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;
 
@@ -78,7 +80,7 @@
             mRil.writeMetricsCallRing(response);
         }
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CALL_RING, response);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_CALL_RING, response);
 
         if (mRil.mRingRegistrant != null) {
             mRil.mRingRegistrant.notifyRegistrant(new AsyncResult(null, response, null));
@@ -90,9 +92,9 @@
      * @param indicationType Type of radio indication
      */
     public void callStateChanged(int indicationType) {
-        mRil.processIndication(RIL.VOICE_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_VOICE, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED);
 
         mRil.mCallStateRegistrants.notifyRegistrants();
     }
@@ -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
@@ -121,7 +123,7 @@
         notification.numberType = callWaitingRecord.numberType;
         notification.numberPlan = callWaitingRecord.numberPlan;
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CDMA_CALL_WAITING, notification);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_CDMA_CALL_WAITING, notification);
 
         mRil.mCallWaitingInfoRegistrants.notifyRegistrants(
                 new AsyncResult(null, notification, null));
@@ -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];
@@ -220,7 +222,9 @@
                             + CdmaInformationRecords.idToString(id) + " ");
             }
 
-            if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CDMA_INFO_REC, cdmaInformationRecords);
+            if (mRil.isLogOrTrace()) {
+                mRil.unsljLogRet(RIL_UNSOL_CDMA_INFO_REC, cdmaInformationRecords);
+            }
             mRil.notifyRegistrantsCdmaInfoRec(cdmaInformationRecords);
         }
     }
@@ -231,11 +235,13 @@
      * @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};
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_CDMA_OTA_PROVISION_STATUS, response);
+        if (mRil.isLogOrTrace()) {
+            mRil.unsljLogRet(RIL_UNSOL_CDMA_OTA_PROVISION_STATUS, response);
+        }
 
         mRil.mOtaProvisionRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
     }
@@ -248,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) {
@@ -259,7 +265,7 @@
             response.add(emergencyNumber);
         }
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_EMERGENCY_NUMBER_LIST, response);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_EMERGENCY_NUMBER_LIST, response);
 
         // Cache emergency number list from last indication.
         mRil.cacheEmergencyNumberListIndication(response);
@@ -275,9 +281,9 @@
      * @param indicationType Type of radio indication
      */
     public void enterEmergencyCallbackMode(int indicationType) {
-        mRil.processIndication(RIL.VOICE_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_VOICE, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_ENTER_EMERGENCY_CALLBACK_MODE);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_ENTER_EMERGENCY_CALLBACK_MODE);
 
         if (mRil.mEmergencyCallbackModeRegistrant != null) {
             mRil.mEmergencyCallbackModeRegistrant.notifyRegistrant();
@@ -290,9 +296,9 @@
      * @param indicationType Type of radio indication
      */
     public void exitEmergencyCallbackMode(int indicationType) {
-        mRil.processIndication(RIL.VOICE_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_VOICE, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_EXIT_EMERGENCY_CALLBACK_MODE);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_EXIT_EMERGENCY_CALLBACK_MODE);
 
         mRil.mExitEmergencyCallbackModeRegistrants.notifyRegistrants();
     }
@@ -303,9 +309,9 @@
      * @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 (RIL.RILJ_LOGD) mRil.unsljLogvRet(RIL_UNSOL_RINGBACK_TONE, start);
+        if (mRil.isLogOrTrace()) mRil.unsljLogvRet(RIL_UNSOL_RINGBACK_TONE, start);
 
         mRil.mRingbackToneRegistrants.notifyRegistrants(new AsyncResult(null, start, null));
     }
@@ -318,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();
@@ -355,7 +361,7 @@
             }
         }
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_ON_SS, ssData);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_ON_SS, ssData);
 
         if (mRil.mSsRegistrant != null) {
             mRil.mSsRegistrant.notifyRegistrant(new AsyncResult(null, ssData, null));
@@ -370,9 +376,9 @@
      * @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 (RIL.RILJ_LOGD) mRil.unsljLogMore(RIL_UNSOL_ON_USSD, "" + ussdModeType);
+        if (mRil.isLogOrTrace()) mRil.unsljLogMore(RIL_UNSOL_ON_USSD, "" + ussdModeType);
 
         // TODO: Clean this up with a parcelable class for better self-documentation
         String[] resp = new String[]{"" + ussdModeType, msg};
@@ -386,9 +392,9 @@
      * @param indicationType Type of radio indication
      */
     public void resendIncallMute(int indicationType) {
-        mRil.processIndication(RIL.VOICE_SERVICE, indicationType);
+        mRil.processIndication(HAL_SERVICE_VOICE, indicationType);
 
-        if (RIL.RILJ_LOGD) mRil.unsljLog(RIL_UNSOL_RESEND_INCALL_MUTE);
+        if (mRil.isLogOrTrace()) mRil.unsljLog(RIL_UNSOL_RESEND_INCALL_MUTE);
 
         mRil.mResendIncallMuteRegistrants.notifyRegistrants();
     }
@@ -399,11 +405,11 @@
      * @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};
 
-        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_SRVCC_STATE_NOTIFY, response);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_SRVCC_STATE_NOTIFY, response);
 
         mRil.writeMetricsSrvcc(state);
         mRil.mSrvccStateRegistrants.notifyRegistrants(new AsyncResult(null, response, null));
@@ -415,9 +421,9 @@
      * @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 (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_STK_CC_ALPHA_NOTIFY, alpha);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_STK_CC_ALPHA_NOTIFY, alpha);
 
         if (mRil.mCatCcAlphaRegistrant != null) {
             mRil.mCatCcAlphaRegistrant.notifyRegistrant(new AsyncResult(null, alpha, null));
@@ -430,9 +436,9 @@
      * @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 (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_STK_CALL_SETUP, timeout);
+        if (mRil.isLogOrTrace()) mRil.unsljLogRet(RIL_UNSOL_STK_CALL_SETUP, timeout);
 
         if (mRil.mCatCallSetUpRegistrant != null) {
             mRil.mCatCallSetUpRegistrant.notifyRegistrant(new AsyncResult(null, timeout, null));
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 d6f69e2..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;
@@ -245,9 +246,10 @@
                 System.arraycopy(pdu, dataIndex, intentData, 0, intentData.length);
             }
 
-            int[] subIds = SubscriptionManager.getSubId(phoneId);
-            int subId = (subIds != null) && (subIds.length > 0) ? subIds[0]
-                    : SmsManager.getDefaultSmsSubscriptionId();
+            int subId = SubscriptionManager.getSubscriptionId(phoneId);
+            if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+                subId = SmsManager.getDefaultSmsSubscriptionId();
+            }
 
             // Continue if PDU parsing fails: the default messaging app may successfully parse the
             // same PDU.
@@ -391,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
@@ -409,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 9ed5eb9..fa2b19b 100644
--- a/src/java/com/android/internal/telephony/cat/CatService.java
+++ b/src/java/com/android/internal/telephony/cat/CatService.java
@@ -20,25 +20,36 @@
 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.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;
 import com.android.internal.telephony.uicc.IccRecords;
@@ -138,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(
@@ -187,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));
     }
 
     /**
@@ -200,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;
@@ -227,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]);
@@ -447,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)
@@ -536,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);
@@ -808,9 +964,9 @@
     //TODO Need to take care for MSIM
     public static AppInterface getInstance() {
         int slotId = PhoneConstants.DEFAULT_SLOT_INDEX;
-        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);
     }
@@ -845,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/CdmaSMSDispatcher.java b/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
index 5a830a8..ebc6342 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
@@ -93,6 +93,21 @@
     }
 
     @Override
+    protected SmsMessageBase.SubmitPduBase getSubmitPdu(String scAddr, String destAddr,
+            String message, boolean statusReportRequested, SmsHeader smsHeader, int priority,
+            int validityPeriod, int messageRef) {
+        return SMSDispatcherUtil.getSubmitPduCdma(scAddr, destAddr, message,
+                statusReportRequested, smsHeader, priority);
+    }
+
+    @Override
+    protected SmsMessageBase.SubmitPduBase getSubmitPdu(String scAddr, String destAddr,
+            int destPort, byte[] message, boolean statusReportRequested, int messageRef) {
+        return SMSDispatcherUtil.getSubmitPduCdma(scAddr, destAddr, destPort, message,
+                statusReportRequested);
+    }
+
+    @Override
     protected TextEncodingDetails calculateLength(CharSequence messageBody, boolean use7bitOnly) {
         return SMSDispatcherUtil.calculateLengthCdma(messageBody, use7bitOnly);
     }
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/cdnr/CarrierDisplayNameResolver.java b/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java
index 6299dac..0ae1b5c 100644
--- a/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java
+++ b/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java
@@ -482,7 +482,7 @@
             }
         } else if (mPhone.getServiceStateTracker().getCombinedRegState(getServiceState())
                 == ServiceState.STATE_IN_SERVICE) {
-            if (mPhone.isWifiCallingEnabled()) {
+            if (mPhone.isWifiCallingEnabled() && mPhone.isImsRegistered()) {
                 data = getCarrierDisplayNameFromWifiCallingOverride(data);
                 if (DBG) {
                     Rlog.d(TAG, "CarrierName override by wifi-calling " + data);
diff --git a/src/java/com/android/internal/telephony/data/AccessNetworksManager.java b/src/java/com/android/internal/telephony/data/AccessNetworksManager.java
index 2a12ecc..267f70b 100644
--- a/src/java/com/android/internal/telephony/data/AccessNetworksManager.java
+++ b/src/java/com/android/internal/telephony/data/AccessNetworksManager.java
@@ -18,23 +18,20 @@
 
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
-import android.annotation.StringDef;
-import android.content.BroadcastReceiver;
+import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
+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.os.UserHandle;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
@@ -55,17 +52,13 @@
 import android.util.SparseArray;
 
 import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.RIL;
-import com.android.internal.telephony.dataconnection.DataThrottler;
+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.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -81,35 +74,14 @@
  */
 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 {}
+    /** 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;
 
     /**
-     * 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}.
+     * The counters to detect frequent QNS attempt to change preferred network transport by ApnType.
      */
-    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";
+    private final @NonNull SparseArray<SlidingWindowEventCounter> mApnTypeToQnsChangeNetworkCounter;
 
     private final String mLogTag;
     private final LocalLog mLocalLog = new LocalLog(64);
@@ -124,13 +96,16 @@
             ApnSetting.TYPE_CBS,
             ApnSetting.TYPE_SUPL,
             ApnSetting.TYPE_EMERGENCY,
-            ApnSetting.TYPE_XCAP
+            ApnSetting.TYPE_XCAP,
+            ApnSetting.TYPE_DUN
     };
 
     private final Phone mPhone;
 
     private final CarrierConfigManager mCarrierConfigManager;
 
+    private @Nullable DataConfigManager mDataConfigManager;
+
     private IQualifiedNetworksService mIQualifiedNetworksService;
 
     private AccessNetworksManagerDeathRecipient mDeathRecipient;
@@ -146,38 +121,10 @@
 
     private final RegistrantList mQualifiedNetworksChangedRegistrants = new RegistrantList();
 
-    private final Set<DataThrottler> mDataThrottlers = new HashSet<>();
-
-    private final BroadcastReceiver mConfigChangedReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(action)
-                    && mPhone.getPhoneId() == intent.getIntExtra(
-                    CarrierConfigManager.EXTRA_SLOT_INDEX, 0)) {
-                // 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();
-            }
-        }
-    };
-
-    /**
-     * The current transport of the APN type. The key is the APN type, and the value is the
-     * transport.
-     */
-    private final Map<Integer, Integer> mCurrentTransports = new ConcurrentHashMap<>();
-
     /**
      * The preferred transport of the APN type. The key is the APN type, and the value is the
-     * transport. The preferred transports are updated as soon as QNS changes the preference, while
-     * the current transports are updated after handover complete.
+     * transport. The preferred transports are updated as soon as QNS changes the preference.
      */
-    // TODO: Deprecate mPreferredTransports. Should expose mAvailableNetworks to
-    //  DataNetworkController after we support multi preferred access networks (i.e.
-    //  DataNetworkController might select 2nd preferred access network in some scenarios.)
     private final Map<Integer, Integer> mPreferredTransports = new ConcurrentHashMap<>();
 
     /**
@@ -187,21 +134,6 @@
             new ArraySet<>();
 
     /**
-     * Registers the data throttler in order to receive APN status changes.
-     *
-     * @param dataThrottler the data throttler to register
-     */
-    public void registerDataThrottler(DataThrottler dataThrottler) {
-        this.post(() -> {
-            QualifiedNetworksServiceConnection serviceConnection = mServiceConnection;
-            this.mDataThrottlers.add(dataThrottler);
-            if (serviceConnection != null) {
-                serviceConnection.registerDataThrottler(dataThrottler);
-            }
-        });
-    }
-
-    /**
      * Represents qualified network types list on a specific APN type.
      */
     public static class QualifiedNetworks {
@@ -229,29 +161,32 @@
         }
     }
 
+    @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() {
             // TODO: try to rebind the service.
             String message = "Qualified network service " + mLastBoundPackageName + " died.";
+            // clear the anomaly report counters when QNS crash
+            mApnTypeToQnsChangeNetworkCounter.clear();
             loge(message);
             AnomalyReporter.reportAnomaly(mAnomalyUUID, message, mPhone.getCarrierId());
         }
     }
 
     private final class QualifiedNetworksServiceConnection implements ServiceConnection {
-
-        /**
-         * The APN throttle status callback is attached to the service connection so that they have
-         * the same life cycle.
-         */
-        @NonNull
-        private final ThrottleStatusChangedCallback mThrottleStatusCallback;
-
-        QualifiedNetworksServiceConnection() {
-            mThrottleStatusCallback = new ThrottleStatusChangedCallback();
-        }
-
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
             if (DBG) log("onServiceConnected " + name);
@@ -263,9 +198,6 @@
                 service.linkToDeath(mDeathRecipient, 0 /* flags */);
                 mIQualifiedNetworksService.createNetworkAvailabilityProvider(mPhone.getPhoneId(),
                         new QualifiedNetworksServiceCallback());
-
-                registerDataThrottlersFirstTime();
-
             } catch (RemoteException e) {
                 loge("Remote exception. " + e);
             }
@@ -274,55 +206,9 @@
         @Override
         public void onServiceDisconnected(ComponentName name) {
             if (DBG) log("onServiceDisconnected " + name);
-            unregisterForThrottleCallbacks();
             mTargetBindingPackageName = null;
         }
 
-        /**
-         * Runs on all of the data throttlers when the service is connected
-         */
-        private void registerDataThrottlersFirstTime() {
-            post(() -> {
-                for (DataThrottler dataThrottler : mDataThrottlers) {
-                    dataThrottler.registerForThrottleStatusChanges(mThrottleStatusCallback);
-                }
-            });
-        }
-
-        private void registerDataThrottler(DataThrottler dataThrottler) {
-            post(() -> {
-                dataThrottler.registerForThrottleStatusChanges(mThrottleStatusCallback);
-            });
-        }
-
-        private void unregisterForThrottleCallbacks() {
-            post(() -> {
-                for (DataThrottler dataThrottler : mDataThrottlers) {
-                    dataThrottler.unregisterForThrottleStatusChanges(mThrottleStatusCallback);
-                }
-            });
-        }
-    }
-
-    private class ThrottleStatusChangedCallback implements DataThrottler.Callback {
-        @Override
-        public void onThrottleStatusChanged(List<ThrottleStatus> throttleStatuses) {
-            post(() -> {
-                try {
-                    List<ThrottleStatus> throttleStatusesBySlot =
-                            throttleStatuses
-                                    .stream()
-                                    .filter(x -> x.getSlotIndex() == mPhone.getPhoneId())
-                                    .collect(Collectors.toList());
-                    if (mIQualifiedNetworksService != null) {
-                        mIQualifiedNetworksService.reportThrottleStatusChanged(mPhone.getPhoneId(),
-                                throttleStatusesBySlot);
-                    }
-                } catch (Exception ex) {
-                    loge("onThrottleStatusChanged", ex);
-                }
-            });
-        }
     }
 
     private final class QualifiedNetworksServiceCallback extends
@@ -344,12 +230,23 @@
             if (Arrays.stream(qualifiedNetworkTypes).anyMatch(accessNetwork
                     -> !DataUtils.isValidAccessNetwork(accessNetwork))) {
                 loge("Invalid access networks " + Arrays.toString(qualifiedNetworkTypes));
+                if (mDataConfigManager != null
+                        && mDataConfigManager.isInvalidQnsParamAnomalyReportEnabled()) {
+                    reportAnomaly("QNS requested invalid Network Type",
+                            "3e89a3df-3524-45fa-b5f2-0fb0e4c77ec4");
+                }
                 return;
             }
 
             List<QualifiedNetworks> qualifiedNetworksList = new ArrayList<>();
+            int satisfiedApnTypes = 0;
             for (int apnType : SUPPORTED_APN_TYPES) {
                 if ((apnTypes & apnType) == apnType) {
+                    // skip the APN anomaly detection if not using the T data stack
+                    if (mDataConfigManager != null) {
+                        satisfiedApnTypes |= apnType;
+                    }
+
                     if (mAvailableNetworks.get(apnType) != null) {
                         if (Arrays.equals(mAvailableNetworks.get(apnType),
                                 qualifiedNetworkTypes)) {
@@ -382,6 +279,16 @@
                 }
             }
 
+            // Report anomaly if any requested APN types are unsatisfied
+            if (satisfiedApnTypes != apnTypes
+                    && mDataConfigManager != null
+                    && mDataConfigManager.isInvalidQnsParamAnomalyReportEnabled()) {
+                int unsatisfied = satisfiedApnTypes ^ apnTypes;
+                reportAnomaly("QNS requested unsupported APN Types:"
+                        + Integer.toBinaryString(unsatisfied),
+                        "3e89a3df-3524-45fa-b5f2-0fb0e4c77ec5");
+            }
+
             if (!qualifiedNetworksList.isEmpty()) {
                 setPreferredTransports(qualifiedNetworksList);
                 mQualifiedNetworksChangedRegistrants.notifyResult(qualifiedNetworksList);
@@ -389,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}.
      */
@@ -422,33 +343,27 @@
         mCarrierConfigManager = (CarrierConfigManager) phone.getContext().getSystemService(
                 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};
-            IntentFilter intentFilter = new IntentFilter();
-            intentFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-            try {
-                Context contextAsUser = phone.getContext().createPackageContextAsUser(
-                        phone.getContext().getPackageName(), 0, UserHandle.ALL);
-                contextAsUser.registerReceiver(mConfigChangedReceiver, intentFilter,
-                        null /* broadcastPermission */, null);
-            } catch (PackageManager.NameNotFoundException e) {
-                loge("Package name not found: ", e);
-            }
-            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();
 
-        if (phone.isUsingNewDataStack()) {
-            // Using post to delay the registering because data retry manager instance is created
-            // later than access networks manager.
-            post(() -> mPhone.getDataNetworkController().getDataRetryManager().registerCallback(
+        // Using post to delay the registering because data retry manager and data config
+        // manager instances are created later than access networks manager.
+        post(() -> {
+            mPhone.getDataNetworkController().getDataRetryManager().registerCallback(
                     new DataRetryManager.DataRetryManagerCallback(this::post) {
                         @Override
                         public void onThrottleStatusChanged(List<ThrottleStatus> throttleStatuses) {
@@ -462,8 +377,29 @@
                                 loge("onThrottleStatusChanged: ", ex);
                             }
                         }
-                    }));
-        }
+                    });
+            mDataConfigManager = mPhone.getDataNetworkController().getDataConfigManager();
+            mDataConfigManager.registerCallback(
+                    new DataConfigManager.DataConfigManagerCallback(this::post) {
+                        @Override
+                        public void onDeviceConfigChanged() {
+                            mApnTypeToQnsChangeNetworkCounter.clear();
+                        }
+                    });
+            mPhone.registerForEmergencyDomainSelected(
+                    this, EVENT_GUIDE_TRANSPORT_TYPE_FOR_EMERGENCY, null);
+        });
+    }
+
+    /**
+     * Trigger the anomaly report with the specified UUID.
+     *
+     * @param anomalyMsg Description of the event
+     * @param uuid UUID associated with that event
+     */
+    private void reportAnomaly(@NonNull String anomalyMsg, @NonNull String uuid) {
+        logl(anomalyMsg);
+        AnomalyReporter.reportAnomaly(UUID.fromString(uuid), anomalyMsg, mPhone.getCarrierId());
     }
 
     /**
@@ -527,24 +463,29 @@
     /**
      * 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
         String packageName = mPhone.getContext().getResources().getString(
                 com.android.internal.R.string.config_qualified_networks_service_package);
 
-        PersistableBundle b = mCarrierConfigManager.getConfigForSubId(mPhone.getSubId());
-
-        if (b != null) {
-            // If carrier config overrides it, use the one from carrier config
-            String carrierConfigPackageName =  b.getString(CarrierConfigManager
-                    .KEY_CARRIER_QUALIFIED_NETWORKS_SERVICE_PACKAGE_OVERRIDE_STRING);
-            if (!TextUtils.isEmpty(carrierConfigPackageName)) {
-                if (DBG) log("Found carrier config override " + carrierConfigPackageName);
-                packageName = carrierConfigPackageName;
+        PersistableBundle b;
+        try {
+            b = mCarrierConfigManager.getConfigForSubId(mPhone.getSubId(),
+                    CarrierConfigManager
+                            .KEY_CARRIER_QUALIFIED_NETWORKS_SERVICE_PACKAGE_OVERRIDE_STRING);
+            if (b != null && !b.isEmpty()) {
+                // If carrier config overrides it, use the one from carrier config
+                String carrierConfigPackageName = b.getString(CarrierConfigManager
+                        .KEY_CARRIER_QUALIFIED_NETWORKS_SERVICE_PACKAGE_OVERRIDE_STRING);
+                if (!TextUtils.isEmpty(carrierConfigPackageName)) {
+                    if (DBG) log("Found carrier config override " + carrierConfigPackageName);
+                    packageName = carrierConfigPackageName;
+                }
             }
+        } catch (RuntimeException e) {
+            loge("Carrier config loader is not available.");
         }
 
         return packageName;
@@ -560,16 +501,22 @@
         String className = mPhone.getContext().getResources().getString(
                 com.android.internal.R.string.config_qualified_networks_service_class);
 
-        PersistableBundle b = mCarrierConfigManager.getConfigForSubId(mPhone.getSubId());
-
-        if (b != null) {
-            // If carrier config overrides it, use the one from carrier config
-            String carrierConfigClassName =  b.getString(CarrierConfigManager
-                    .KEY_CARRIER_QUALIFIED_NETWORKS_SERVICE_CLASS_OVERRIDE_STRING);
-            if (!TextUtils.isEmpty(carrierConfigClassName)) {
-                if (DBG) log("Found carrier config override " + carrierConfigClassName);
-                className = carrierConfigClassName;
+        PersistableBundle b;
+        try {
+            b = mCarrierConfigManager.getConfigForSubId(mPhone.getSubId(),
+                    CarrierConfigManager
+                            .KEY_CARRIER_QUALIFIED_NETWORKS_SERVICE_CLASS_OVERRIDE_STRING);
+            if (b != null && !b.isEmpty()) {
+                // If carrier config overrides it, use the one from carrier config
+                String carrierConfigClassName = b.getString(CarrierConfigManager
+                        .KEY_CARRIER_QUALIFIED_NETWORKS_SERVICE_CLASS_OVERRIDE_STRING);
+                if (!TextUtils.isEmpty(carrierConfigClassName)) {
+                    if (DBG) log("Found carrier config override " + carrierConfigClassName);
+                    className = carrierConfigClassName;
+                }
             }
+        } catch (RuntimeException e) {
+            loge("Carrier config loader is not available.");
         }
 
         return className;
@@ -605,87 +552,12 @@
     }
 
     /**
-     * @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;
     }
 
-    /**
-     * Get the transport based on the network capability.
-     *
-     * @param netCap The network capability.
-     * @return The transport type.
-     */
-    public @TransportType int getCurrentTransportByNetworkCapability(@NetCapability int netCap) {
-        return getCurrentTransport(DataUtils.networkCapabilityToApnType(netCap));
-    }
-
-    /**
-     * Get the transport based on the APN type.
-     *
-     * @param apnType APN type
-     * @return The transport type
-     */
-    // TODO: Remove this after TransportManager is removed.
-    public @TransportType int getCurrentTransport(@ApnType int apnType) {
-        // In legacy mode, always route to cellular.
-        if (isInLegacyMode()) {
-            return AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
-        }
-
-        // If we can't find the corresponding transport, always route to cellular.
-        return mCurrentTransports.get(apnType) == null
-                ? AccessNetworkConstants.TRANSPORT_TYPE_WWAN : mCurrentTransports.get(apnType);
-    }
-
-    /**
-     * Set the current transport of a network capability.
-     *
-     * @param netCap The network capability.
-     * @param transport The transport.
-     */
-    public void setCurrentTransportByNetworkCapability(@NetCapability int netCap,
-            @TransportType int transport) {
-        setCurrentTransport(DataUtils.networkCapabilityToApnType(netCap), transport);
-    }
-
-    /**
-     * Set the current transport of apn type.
-     *
-     * @param apnType The APN type
-     * @param transport The transport.
-     */
-    // TODO: Remove this after TransportManager is removed.
-    public void setCurrentTransport(@ApnType int apnType, @TransportType int transport) {
-        Integer previousTransport = mCurrentTransports.put(apnType, transport);
-        if (previousTransport == null || previousTransport != transport) {
-            logl("setCurrentTransport: apnType=" + ApnSetting.getApnTypeString(apnType)
-                    + ", transport=" + AccessNetworkConstants.transportTypeToString(transport));
-        }
-    }
-
     private static @TransportType int getTransportFromAccessNetwork(int accessNetwork) {
         return accessNetwork == AccessNetworkType.IWLAN
                 ? AccessNetworkConstants.TRANSPORT_TYPE_WLAN
@@ -717,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);
     }
@@ -744,20 +611,14 @@
     }
 
     /**
-     * Check if there is any APN type's current transport is on IWLAN.
+     * Check if there is any APN type preferred on IWLAN.
      *
      * @return {@code true} if there is any APN is on IWLAN, otherwise {@code false}.
      */
     public boolean isAnyApnOnIwlan() {
         for (int apnType : AccessNetworksManager.SUPPORTED_APN_TYPES) {
-            if (mPhone.isUsingNewDataStack()) {
-                if (getPreferredTransport(apnType) == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
-                    return true;
-                }
-            } else {
-                if (getCurrentTransport(apnType) == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
-                    return true;
-                }
+            if (getPreferredTransport(apnType) == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
+                return true;
             }
         }
         return false;
@@ -821,14 +682,6 @@
         IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
         pw.println(AccessNetworksManager.class.getSimpleName() + "-" + mPhone.getPhoneId() + ":");
         pw.increaseIndent();
-        pw.println("current transports=");
-        pw.increaseIndent();
-        for (int apnType : AccessNetworksManager.SUPPORTED_APN_TYPES) {
-            pw.println(ApnSetting.getApnTypeString(apnType)
-                    + ": " + AccessNetworkConstants.transportTypeToString(
-                    getCurrentTransport(apnType)));
-        }
-        pw.decreaseIndent();
         pw.println("preferred transports=");
         pw.increaseIndent();
         for (int apnType : AccessNetworksManager.SUPPORTED_APN_TYPES) {
@@ -838,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 c63676f..c1d1203 100644
--- a/src/java/com/android/internal/telephony/data/CellularNetworkValidator.java
+++ b/src/java/com/android/internal/telephony/data/CellularNetworkValidator.java
@@ -17,7 +17,7 @@
 package com.android.internal.telephony.data;
 
 import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
-import static android.telephony.CarrierConfigManager.KEY_DATA_SWITCH_VALIDATION_MIN_GAP_LONG;
+import static android.telephony.CarrierConfigManager.KEY_DATA_SWITCH_VALIDATION_MIN_INTERVAL_MILLIS_LONG;
 import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
 
 import android.content.Context;
@@ -40,9 +40,10 @@
 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;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
 
 import java.util.Comparator;
 import java.util.HashMap;
@@ -164,9 +165,9 @@
 
         private String getValidationNetworkIdentity(int subId) {
             if (!SubscriptionManager.isUsableSubscriptionId(subId)) return null;
-            SubscriptionController subController = SubscriptionController.getInstance();
-            if (subController == null) return null;
-            Phone 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(
@@ -192,7 +193,7 @@
             if (configManager != null) {
                 PersistableBundle b = configManager.getConfigForSubId(subId);
                 if (b != null) {
-                    ttl = b.getLong(KEY_DATA_SWITCH_VALIDATION_MIN_GAP_LONG);
+                    ttl = b.getLong(KEY_DATA_SWITCH_VALIDATION_MIN_INTERVAL_MILLIS_LONG);
                 }
             }
             // Ttl can't be bigger than one day for now.
@@ -257,7 +258,9 @@
         // If it's already validating the same subscription, do nothing.
         if (subId == mSubId) return;
 
-        if (!SubscriptionController.getInstance().isActiveSubId(subId)) {
+        SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
+                .getSubscriptionInfoInternal(subId);
+        if (subInfo == null || !subInfo.isActive()) {
             logd("Failed to start validation. Inactive subId " + subId);
             callback.onValidationDone(false, subId);
             return;
diff --git a/src/java/com/android/internal/telephony/data/DataConfigManager.java b/src/java/com/android/internal/telephony/data/DataConfigManager.java
index 82a4ccd..78450a8 100644
--- a/src/java/com/android/internal/telephony/data/DataConfigManager.java
+++ b/src/java/com/android/internal/telephony/data/DataConfigManager.java
@@ -16,12 +16,9 @@
 
 package com.android.internal.telephony.data;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.StringDef;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.res.Resources;
 import android.net.LinkProperties;
 import android.net.NetworkCapabilities;
@@ -29,7 +26,6 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.PersistableBundle;
-import android.os.RegistrantList;
 import android.provider.DeviceConfig;
 import android.telephony.Annotation.ApnType;
 import android.telephony.Annotation.NetCapability;
@@ -41,6 +37,7 @@
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -59,9 +56,11 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
 import java.util.stream.Collectors;
 
 /**
@@ -132,8 +131,7 @@
     private static final String DATA_CONFIG_NETWORK_TYPE_IDEN = "iDEN";
 
     /** Network type LTE. Should not be used outside of DataConfigManager. */
-    // TODO: Public only for use by DcTracker. This should be private once DcTracker is removed.
-    public static final String DATA_CONFIG_NETWORK_TYPE_LTE = "LTE";
+    private static final String DATA_CONFIG_NETWORK_TYPE_LTE = "LTE";
 
     /** Network type HSPA+. Should not be used outside of DataConfigManager. */
     private static final String DATA_CONFIG_NETWORK_TYPE_HSPAP = "HSPA+";
@@ -151,12 +149,10 @@
     private static final String DATA_CONFIG_NETWORK_TYPE_LTE_CA = "LTE_CA";
 
     /** Network type NR_NSA. Should not be used outside of DataConfigManager. */
-    // TODO: Public only for use by DcTracker. This should be private once DcTracker is removed.
-    public static final String DATA_CONFIG_NETWORK_TYPE_NR_NSA = "NR_NSA";
+    private static final String DATA_CONFIG_NETWORK_TYPE_NR_NSA = "NR_NSA";
 
     /** Network type NR_NSA_MMWAVE. Should not be used outside of DataConfigManager. */
-    // TODO: Public only for use by DcTracker. This should be private once DcTracker is removed.
-    public static final String DATA_CONFIG_NETWORK_TYPE_NR_NSA_MMWAVE = "NR_NSA_MMWAVE";
+    private static final String DATA_CONFIG_NETWORK_TYPE_NR_NSA_MMWAVE = "NR_NSA_MMWAVE";
 
     /** Network type NR_SA. Should not be used outside of DataConfigManager. */
     private static final String DATA_CONFIG_NETWORK_TYPE_NR_SA = "NR_SA";
@@ -192,6 +188,10 @@
     @Retention(RetentionPolicy.SOURCE)
     private @interface DataConfigNetworkType {}
 
+    /** Data config update callbacks. */
+    private final @NonNull Set<DataConfigManagerCallback> mDataConfigManagerCallbacks =
+            new ArraySet<>();
+
     /** DeviceConfig key of anomaly report threshold for back to back ims release-request. */
     private static final String KEY_ANOMALY_IMS_RELEASE_REQUEST = "anomaly_ims_release_request";
     /** DeviceConfig key of anomaly report threshold for frequent setup data failure. */
@@ -199,6 +199,10 @@
             "anomaly_setup_data_call_failure";
     /** DeviceConfig key of anomaly report threshold for frequent network-unwanted call. */
     private static final String KEY_ANOMALY_NETWORK_UNWANTED = "anomaly_network_unwanted";
+    /** DeviceConfig key of anomaly report threshold for frequent change of preferred network. */
+    private static final String KEY_ANOMALY_QNS_CHANGE_NETWORK = "anomaly_qns_change_network";
+    /** DeviceConfig key of anomaly report threshold for invalid QNS params. */
+    private static final String KEY_ANOMALY_QNS_PARAM = "anomaly_qns_param";
     /** DeviceConfig key of anomaly report threshold for DataNetwork stuck in connecting state. */
     private static final String KEY_ANOMALY_NETWORK_CONNECTING_TIMEOUT =
             "anomaly_network_connecting_timeout";
@@ -208,6 +212,14 @@
     /** DeviceConfig key of anomaly report threshold for DataNetwork stuck in handover state. */
     private static final String KEY_ANOMALY_NETWORK_HANDOVER_TIMEOUT =
             "anomaly_network_handover_timeout";
+    /** DeviceConfig key of anomaly report: True for enabling APN config invalidity detection */
+    private static final String KEY_ANOMALY_APN_CONFIG_ENABLED = "anomaly_apn_config_enabled";
+    /** DeviceConfig key of the time threshold in ms for defining a network status to be stable. **/
+    private static final String KEY_AUTO_DATA_SWITCH_AVAILABILITY_STABILITY_TIME_THRESHOLD =
+            "auto_data_switch_availability_stability_time_threshold";
+    /** DeviceConfig key of the maximum number of retries when a validation for switching failed.**/
+    private static final String KEY_AUTO_DATA_SWITCH_VALIDATION_MAX_RETRY =
+            "auto_data_switch_validation_max_retry";
 
     /** Anomaly report thresholds for frequent setup data call failure. */
     private EventFrequency mSetupDataCallAnomalyReportThreshold;
@@ -222,6 +234,12 @@
     private EventFrequency mNetworkUnwantedAnomalyReportThreshold;
 
     /**
+     * {@code true} if enabled anomaly detection for param when QNS wants to change preferred
+     * network at {@link AccessNetworksManager}.
+     */
+    private boolean mIsInvalidQnsParamAnomalyReportEnabled;
+
+    /**
      * Timeout in ms before creating an anomaly report for a DataNetwork stuck in
      * {@link DataNetwork.ConnectingState}.
      */
@@ -239,12 +257,14 @@
      */
     private int mNetworkHandoverTimeout;
 
+    /**
+     * True if enabled anomaly detection for APN config that's read from {@link DataProfileManager}
+     */
+    private boolean mIsApnConfigAnomalyReportEnabled;
+
     private @NonNull final Phone mPhone;
     private @NonNull final String mLogTag;
 
-    /** The registrants list for config update event. */
-    private @NonNull final RegistrantList mConfigUpdateRegistrants = new RegistrantList();
-
     private @NonNull final CarrierConfigManager mCarrierConfigManager;
     private @NonNull PersistableBundle mCarrierConfig = null;
     private @NonNull Resources mResources = null;
@@ -263,6 +283,8 @@
     /** The network types that only support single data networks */
     private @NonNull final @NetworkType List<Integer> mSingleDataNetworkTypeList =
             new ArrayList<>();
+    private @NonNull final @NetCapability Set<Integer> mCapabilitiesExemptFromSingleDataList =
+            new HashSet<>();
     /** The network types that support temporarily not metered */
     private @NonNull final @DataConfigNetworkType Set<String> mUnmeteredNetworkTypes =
             new HashSet<>();
@@ -277,6 +299,8 @@
             new ConcurrentHashMap<>();
     /** Rules for handover between IWLAN and cellular network. */
     private @NonNull final List<HandoverRule> mHandoverRuleList = new ArrayList<>();
+    /** {@code True} keep IMS network in case of moving to non VOPS area; {@code false} otherwise.*/
+    private boolean mShouldKeepNetworkUpInNonVops = false;
 
     /**
      * Constructor
@@ -292,22 +316,13 @@
         log("DataConfigManager created.");
 
         mCarrierConfigManager = mPhone.getContext().getSystemService(CarrierConfigManager.class);
-
-        // Register for carrier configs update
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        mPhone.getContext().registerReceiver(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                if (intent.getAction().equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
-                    if (mPhone.getPhoneId() == intent.getIntExtra(
-                            CarrierConfigManager.EXTRA_SLOT_INDEX,
-                            SubscriptionManager.INVALID_SIM_SLOT_INDEX)) {
+        // Callback send msg to handler thread, so callback itself can be executed in binder thread.
+        mCarrierConfigManager.registerCarrierConfigChangeListener(Runnable::run,
+                (slotIndex, subId, carrierId, specificCarrierId) -> {
+                    if (slotIndex == mPhone.getPhoneId()) {
                         sendEmptyMessage(EVENT_CARRIER_CONFIG_CHANGED);
                     }
-                }
-            }
-        }, filter, null, mPhone);
+                });
 
         // Register for device config update
         DeviceConfig.addOnPropertiesChangedListener(
@@ -321,8 +336,46 @@
 
         // Must be called to set mCarrierConfig and mResources to non-null values
         updateCarrierConfig();
+        // Must be called to set anomaly report threshold to non-null values
         updateDeviceConfig();
-        mConfigUpdateRegistrants.notifyRegistrants();
+    }
+
+    /**
+     * The data config callback.
+     */
+    public static class DataConfigManagerCallback extends DataCallback {
+        /**
+         * Constructor
+         *
+         * @param executor The executor of the callback.
+         */
+        public DataConfigManagerCallback(@NonNull @CallbackExecutor Executor executor) {
+            super(executor);
+        }
+
+        /** Callback on carrier config update.*/
+        public void onCarrierConfigChanged() {}
+
+        /** Callback on device config update.*/
+        public void onDeviceConfigChanged() {}
+    }
+
+    /**
+     * Register the callback for receiving information from {@link DataConfigManager}.
+     *
+     * @param callback The callback.
+     */
+    public void registerCallback(@NonNull DataConfigManagerCallback callback) {
+        mDataConfigManagerCallbacks.add(callback);
+    }
+
+    /**
+     * Unregister the callback.
+     *
+     * @param callback The previously registered callback.
+     */
+    public void unregisterCallback(@NonNull DataConfigManagerCallback callback) {
+        mDataConfigManagerCallbacks.remove(callback);
     }
 
     @Override
@@ -331,12 +384,14 @@
             case EVENT_CARRIER_CONFIG_CHANGED:
                 log("EVENT_CARRIER_CONFIG_CHANGED");
                 updateCarrierConfig();
-                mConfigUpdateRegistrants.notifyRegistrants();
+                mDataConfigManagerCallbacks.forEach(callback -> callback.invokeFromExecutor(
+                        callback::onCarrierConfigChanged));
                 break;
             case EVENT_DEVICE_CONFIG_CHANGED:
                 log("EVENT_DEVICE_CONFIG_CHANGED");
                 updateDeviceConfig();
-                mConfigUpdateRegistrants.notifyRegistrants();
+                mDataConfigManagerCallbacks.forEach(callback -> callback.invokeFromExecutor(
+                        callback::onDeviceConfigChanged));
                 break;
             default:
                 loge("Unexpected message " + msg.what);
@@ -349,17 +404,13 @@
                 DeviceConfig.getProperties(DeviceConfig.NAMESPACE_TELEPHONY);
 
         mImsReleaseRequestAnomalyReportThreshold = parseSlidingWindowCounterThreshold(
-                properties.getString(KEY_ANOMALY_IMS_RELEASE_REQUEST, null),
-                0,
-                12);
+                properties.getString(KEY_ANOMALY_IMS_RELEASE_REQUEST, null), 0, 2);
         mNetworkUnwantedAnomalyReportThreshold = parseSlidingWindowCounterThreshold(
-                properties.getString(KEY_ANOMALY_NETWORK_UNWANTED, null),
-                0,
-                12);
+                properties.getString(KEY_ANOMALY_NETWORK_UNWANTED, null), 0, 12);
         mSetupDataCallAnomalyReportThreshold = parseSlidingWindowCounterThreshold(
-                properties.getString(KEY_ANOMALY_SETUP_DATA_CALL_FAILURE, null),
-                0,
-                2);
+                properties.getString(KEY_ANOMALY_SETUP_DATA_CALL_FAILURE, null), 0, 12);
+        mIsInvalidQnsParamAnomalyReportEnabled = properties.getBoolean(
+                KEY_ANOMALY_QNS_PARAM, false);
         mNetworkConnectingTimeout = properties.getInt(
                 KEY_ANOMALY_NETWORK_CONNECTING_TIMEOUT, DEFAULT_NETWORK_TRANSIT_STATE_TIMEOUT_MS);
         mNetworkDisconnectingTimeout = properties.getInt(
@@ -367,6 +418,8 @@
                 DEFAULT_NETWORK_TRANSIT_STATE_TIMEOUT_MS);
         mNetworkHandoverTimeout = properties.getInt(
                 KEY_ANOMALY_NETWORK_HANDOVER_TIMEOUT, DEFAULT_NETWORK_TRANSIT_STATE_TIMEOUT_MS);
+        mIsApnConfigAnomalyReportEnabled = properties.getBoolean(
+                KEY_ANOMALY_APN_CONFIG_ENABLED, false);
     }
 
     /**
@@ -393,13 +446,14 @@
         updateNetworkCapabilityPriority();
         updateDataRetryRules();
         updateMeteredApnTypes();
-        updateSingleDataNetworkTypeList();
+        updateSingleDataNetworkTypeAndCapabilityExemption();
+        updateVopsConfig();
         updateUnmeteredNetworkTypes();
         updateBandwidths();
         updateTcpBuffers();
         updateHandoverRules();
 
-        log("Data config updated. Config is " + (isConfigCarrierSpecific() ? "" : "not ")
+        log("Carrier config updated. Config is " + (isConfigCarrierSpecific() ? "" : "not ")
                 + "carrier specific.");
     }
 
@@ -413,7 +467,8 @@
                     CarrierConfigManager.KEY_TELEPHONY_NETWORK_CAPABILITY_PRIORITIES_STRING_ARRAY);
             if (capabilityPriorityStrings != null) {
                 for (String capabilityPriorityString : capabilityPriorityStrings) {
-                    capabilityPriorityString = capabilityPriorityString.trim().toUpperCase();
+                    capabilityPriorityString =
+                            capabilityPriorityString.trim().toUpperCase(Locale.ROOT);
                     String[] tokens = capabilityPriorityString.split(":");
                     if (tokens.length != 2) {
                         loge("Invalid config \"" + capabilityPriorityString + "\"");
@@ -584,15 +639,32 @@
     /**
      * Update the network types for only single data networks from the carrier config.
      */
-    private void updateSingleDataNetworkTypeList() {
+    private void updateSingleDataNetworkTypeAndCapabilityExemption() {
         synchronized (this) {
             mSingleDataNetworkTypeList.clear();
+            mCapabilitiesExemptFromSingleDataList.clear();
             int[] singleDataNetworkTypeList = mCarrierConfig.getIntArray(
                     CarrierConfigManager.KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY);
             if (singleDataNetworkTypeList != null) {
-                Arrays.stream(singleDataNetworkTypeList)
-                        .forEach(mSingleDataNetworkTypeList::add);
+                Arrays.stream(singleDataNetworkTypeList).forEach(mSingleDataNetworkTypeList::add);
             }
+
+            int[] singleDataCapabilitiesExemptList = mCarrierConfig.getIntArray(
+                    CarrierConfigManager.KEY_CAPABILITIES_EXEMPT_FROM_SINGLE_DC_CHECK_INT_ARRAY);
+            if (singleDataCapabilitiesExemptList != null) {
+                Arrays.stream(singleDataCapabilitiesExemptList)
+                        .forEach(mCapabilitiesExemptFromSingleDataList::add);
+            }
+        }
+    }
+
+    /**
+     * Update the voice over PS related config from the carrier config.
+     */
+    private void updateVopsConfig() {
+        synchronized (this) {
+            mShouldKeepNetworkUpInNonVops = mCarrierConfig.getBoolean(CarrierConfigManager
+                    .Ims.KEY_KEEP_PDN_UP_IN_NO_VOPS_BOOL);
         }
     }
 
@@ -604,6 +676,25 @@
     }
 
     /**
+     * @return The list of {@link android.net.NetworkCapabilities.NetCapability} that every of which
+     * is exempt from the single PDN check.
+     */
+    public @NonNull @NetCapability Set<Integer> getCapabilitiesExemptFromSingleDataNetwork() {
+        return Collections.unmodifiableSet(mCapabilitiesExemptFromSingleDataList);
+    }
+
+    /** {@code True} keep IMS network in case of moving to non VOPS area; {@code false} otherwise.*/
+    public boolean shouldKeepNetworkUpInNonVops() {
+        return mShouldKeepNetworkUpInNonVops;
+    }
+
+    /** {@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.
      */
@@ -787,6 +878,14 @@
     }
 
     /**
+     * @return {@code true} if enabled anomaly report for invalid param when QNS wants to change
+     * preferred network at {@link AccessNetworksManager}.
+     */
+    public boolean isInvalidQnsParamAnomalyReportEnabled() {
+        return mIsInvalidQnsParamAnomalyReportEnabled;
+    }
+
+    /**
      * @return Timeout in ms before creating an anomaly report for a DataNetwork stuck in
      * {@link DataNetwork.ConnectingState}.
      */
@@ -811,6 +910,32 @@
     }
 
     /**
+     * @return {@code true} if enabled anomaly report for invalid APN config
+     * at {@link DataProfileManager}
+     */
+    public boolean isApnConfigAnomalyReportEnabled() {
+        return mIsApnConfigAnomalyReportEnabled;
+    }
+
+    /**
+     * @return The maximum number of retries when a validation for switching failed.
+     */
+    public int getAutoDataSwitchValidationMaxRetry() {
+        return mResources.getInteger(com.android.internal.R.integer
+                .auto_data_switch_validation_max_retry);
+    }
+
+    /**
+     * @return 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.
+     */
+    public long getAutoDataSwitchAvailabilityStabilityTimeThreshold() {
+        return mResources.getInteger(com.android.internal.R.integer
+                .auto_data_switch_availability_stability_time_threshold_millis);
+    }
+
+    /**
      * Get the TCP config string, used by {@link LinkProperties#setTcpBufferSizes(String)}.
      * The config string will have the following form, with values in bytes:
      * "read_min,read_default,read_max,write_min,write_default,write_max"
@@ -865,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);
     }
@@ -895,9 +1020,8 @@
      * @param displayInfo The {@link TelephonyDisplayInfo} used to determine the type.
      * @return The equivalent {@link DataConfigNetworkType}.
      */
-    public static @NonNull @DataConfigNetworkType String getDataConfigNetworkType(
+    private static @NonNull @DataConfigNetworkType String getDataConfigNetworkType(
             @NonNull TelephonyDisplayInfo displayInfo) {
-        // TODO: Make method private once DataConnection is removed
         int networkType = displayInfo.getNetworkType();
         switch (displayInfo.getOverrideNetworkType()) {
             case TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED:
@@ -1140,24 +1264,6 @@
     }
 
     /**
-     * Registration point for subscription info ready.
-     *
-     * @param h handler to notify.
-     * @param what what code of message when delivered.
-     */
-    public void registerForConfigUpdate(Handler h, int what) {
-        mConfigUpdateRegistrants.addUnique(h, what, null);
-    }
-
-    /**
-     *
-     * @param h The original handler passed in {@link #registerForConfigUpdate(Handler, int)}.
-     */
-    public void unregisterForConfigUpdate(Handler h) {
-        mConfigUpdateRegistrants.remove(h);
-    }
-
-    /**
      * Log debug messages.
      * @param s debug messages
      */
@@ -1203,15 +1309,27 @@
         pw.println("mSetupDataCallAnomalyReport=" + mSetupDataCallAnomalyReportThreshold);
         pw.println("mNetworkUnwantedAnomalyReport=" + mNetworkUnwantedAnomalyReportThreshold);
         pw.println("mImsReleaseRequestAnomalyReport=" + mImsReleaseRequestAnomalyReportThreshold);
+        pw.println("mIsInvalidQnsParamAnomalyReportEnabled="
+                + mIsInvalidQnsParamAnomalyReportEnabled);
         pw.println("mNetworkConnectingTimeout=" + mNetworkConnectingTimeout);
         pw.println("mNetworkDisconnectingTimeout=" + mNetworkDisconnectingTimeout);
         pw.println("mNetworkHandoverTimeout=" + mNetworkHandoverTimeout);
+        pw.println("mIsApnConfigAnomalyReportEnabled=" + mIsApnConfigAnomalyReportEnabled);
+        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()
                 .map(ApnSetting::getApnTypeString).collect(Collectors.joining(",")));
         pw.println("Single data network types=" + mSingleDataNetworkTypeList.stream()
                 .map(TelephonyManager::getNetworkTypeName).collect(Collectors.joining(",")));
+        pw.println("Capabilities exempt from single PDN=" + mCapabilitiesExemptFromSingleDataList
+                .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));
@@ -1236,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/DataEnabledOverride.java b/src/java/com/android/internal/telephony/data/DataEnabledOverride.java
deleted file mode 100644
index e639ba6..0000000
--- a/src/java/com/android/internal/telephony/data/DataEnabledOverride.java
+++ /dev/null
@@ -1,460 +0,0 @@
-/*
- * 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.data;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.telephony.Annotation.ApnType;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.telephony.data.ApnSetting;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-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.data.DataEnabledOverride.OverrideConditions.Condition;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-
-/**
- * This class represents the rules for overriding data enabled settings in different conditions.
- * When data is disabled by the user, data can still be turned on temporarily when conditions
- * satisfy any rule here.
- */
-public class DataEnabledOverride {
-
-    private final Set<OverrideRule> mRules = new HashSet<>();
-
-    /**
-     * The rule for allowing data during voice call.
-     */
-    private static final OverrideRule OVERRIDE_RULE_ALLOW_DATA_DURING_VOICE_CALL =
-            new OverrideRule(ApnSetting.TYPE_ALL, OverrideConditions.CONDITION_IN_VOICE_CALL
-                    | OverrideConditions.CONDITION_NON_DEFAULT
-                    | OverrideConditions.CONDITION_DEFAULT_DATA_ENABLED
-                    | OverrideConditions.CONDITION_DSDS_ENABLED);
-
-    /**
-     * The rule for always allowing mms. Without adding any condition to the rule, any condition can
-     * satisfy this rule for mms.
-     */
-    private static final OverrideRule OVERRIDE_RULE_ALWAYS_ALLOW_MMS =
-            new OverrideRule(ApnSetting.TYPE_MMS, OverrideConditions.CONDITION_UNCONDITIONALLY);
-
-    /**
-     * Data enabled override rule
-     */
-    private static class OverrideRule {
-        /**
-         * APN type of the rule. The rule is APN type specific. The override is applicable to the
-         * specified APN type as well. For now we only support one APN type per rule. Can be
-         * expanded to multiple APN types in the future.
-         */
-        private final @ApnType int mApnType;
-
-        /** The required conditions for overriding */
-        private final OverrideConditions mRequiredConditions;
-
-        /**
-         * Constructor
-         *
-         * @param rule The override rule string. For example, {@code mms=nonDefault} or
-         * {@code default=voiceCall & nonDefault}
-         */
-        OverrideRule(@NonNull String rule) {
-            String[] tokens = rule.trim().split("\\s*=\\s*");
-            if (tokens.length != 2) {
-                throw new IllegalArgumentException("Invalid data enabled override rule format: "
-                        + rule);
-            }
-
-            if (TextUtils.isEmpty(tokens[0])) {
-                throw new IllegalArgumentException("APN type can't be empty");
-            }
-
-            mApnType = ApnSetting.getApnTypesBitmaskFromString(tokens[0]);
-            if (mApnType == ApnSetting.TYPE_NONE) {
-                throw new IllegalArgumentException("Invalid APN type. Rule=" + rule);
-            }
-
-            mRequiredConditions = new OverrideConditions(tokens[1]);
-        }
-
-        /**
-         * Constructor
-         *
-         * @param apnType APN type of the rule
-         * @param requiredConditions The required conditions for the rule
-         */
-        private OverrideRule(int apnType, int requiredConditions) {
-            mApnType = apnType;
-            mRequiredConditions = new OverrideConditions(requiredConditions);
-        }
-
-        /**
-         * Check if this rule can be satisfied by the given APN type and provided conditions.
-         *
-         * @param apnType APN type to check
-         * @param providedConditions The provided conditions to check
-         * @return {@code true} if satisfied
-         */
-        boolean isSatisfiedByConditions(@ApnType int apnType, @Condition int providedConditions) {
-            return (mApnType == apnType || mApnType == ApnSetting.TYPE_ALL)
-                    && mRequiredConditions.allMet(providedConditions);
-        }
-
-        @Override
-        public String toString() {
-            return ApnSetting.getApnTypeString(mApnType) + "=" + mRequiredConditions;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            OverrideRule that = (OverrideRule) o;
-            return mApnType == that.mApnType
-                    && Objects.equals(mRequiredConditions, that.mRequiredConditions);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(mApnType, mRequiredConditions);
-        }
-    }
-
-    /**
-     * Represent the conditions for overriding data enabled settings
-     */
-    static class OverrideConditions {
-        // Possible values for data enabled override condition. Note these flags are bitmasks.
-        /** Unconditionally override enabled settings */
-        static final int CONDITION_UNCONDITIONALLY = 0;
-
-        /** Enable data only on subscription that is not user selected default data subscription */
-        static final int CONDITION_NON_DEFAULT = 1 << 0;
-
-        /** Enable data only when device has ongoing voice call */
-        static final int CONDITION_IN_VOICE_CALL = 1 << 1;
-
-        /** Enable data only when default data is on */
-        static final int CONDITION_DEFAULT_DATA_ENABLED = 1 << 2;
-
-        /** Enable data only when device is in DSDS mode */
-        static final int CONDITION_DSDS_ENABLED = 1 << 3;
-
-        /** Enable data unconditionally in string format */
-        static final String CONDITION_UNCONDITIONALLY_STRING = "unconditionally";
-
-        /** Enable data only on subscription that is not default in string format */
-        static final String CONDITION_NON_DEFAULT_STRING = "nonDefault";
-
-        /** Enable data only when device has ongoing voice call in string format */
-        static final String CONDITION_VOICE_CALL_STRING = "inVoiceCall";
-
-        /** Enable data only when default data is on in string format */
-        static final String CONDITION_DEFAULT_DATA_ENABLED_STRING = "DefaultDataOn";
-
-        /** Enable data only when device is in DSDS mode in string format */
-        static final String CONDITION_DSDS_ENABLED_STRING = "dsdsEnabled";
-
-        /** @hide */
-        @IntDef(flag = true, prefix = { "OVERRIDE_CONDITION_" }, value = {
-                CONDITION_NON_DEFAULT,
-                CONDITION_IN_VOICE_CALL,
-                CONDITION_DEFAULT_DATA_ENABLED,
-                CONDITION_DSDS_ENABLED
-        })
-        @Retention(RetentionPolicy.SOURCE)
-        public @interface Condition {}
-
-        private static final Map<Integer, String> OVERRIDE_CONDITION_INT_MAP = new ArrayMap<>();
-        private static final Map<String, Integer> OVERRIDE_CONDITION_STRING_MAP = new ArrayMap<>();
-
-        static {
-            OVERRIDE_CONDITION_INT_MAP.put(CONDITION_NON_DEFAULT,
-                    CONDITION_NON_DEFAULT_STRING);
-            OVERRIDE_CONDITION_INT_MAP.put(CONDITION_IN_VOICE_CALL,
-                    CONDITION_VOICE_CALL_STRING);
-            OVERRIDE_CONDITION_INT_MAP.put(CONDITION_DEFAULT_DATA_ENABLED,
-                    CONDITION_DEFAULT_DATA_ENABLED_STRING);
-            OVERRIDE_CONDITION_INT_MAP.put(CONDITION_DSDS_ENABLED,
-                    CONDITION_DSDS_ENABLED_STRING);
-
-            OVERRIDE_CONDITION_STRING_MAP.put(CONDITION_UNCONDITIONALLY_STRING,
-                    CONDITION_UNCONDITIONALLY);
-            OVERRIDE_CONDITION_STRING_MAP.put(CONDITION_NON_DEFAULT_STRING,
-                    CONDITION_NON_DEFAULT);
-            OVERRIDE_CONDITION_STRING_MAP.put(CONDITION_VOICE_CALL_STRING,
-                    CONDITION_IN_VOICE_CALL);
-            OVERRIDE_CONDITION_STRING_MAP.put(CONDITION_DEFAULT_DATA_ENABLED_STRING,
-                    CONDITION_DEFAULT_DATA_ENABLED);
-            OVERRIDE_CONDITION_STRING_MAP.put(CONDITION_DSDS_ENABLED_STRING,
-                    CONDITION_DSDS_ENABLED);
-        }
-
-        private final @Condition int mConditions;
-
-        /**
-         * Conditions for overriding data enabled setting
-         *
-         * @param conditions Conditions in string format
-         */
-        OverrideConditions(@NonNull String conditions) {
-            mConditions = getBitmaskFromString(conditions);
-        }
-
-        /**
-         * Conditions for overriding data enabled setting
-         *
-         * @param conditions Conditions in bitmask
-         */
-        OverrideConditions(@Condition int conditions) {
-            mConditions = conditions;
-        }
-
-        private static String getStringFromBitmask(@Condition int conditions) {
-            if (conditions == CONDITION_UNCONDITIONALLY) {
-                return CONDITION_UNCONDITIONALLY_STRING;
-            }
-            List<String> conditionsStrings = new ArrayList<>();
-            for (Integer condition : OVERRIDE_CONDITION_INT_MAP.keySet()) {
-                if ((conditions & condition) == condition) {
-                    conditionsStrings.add(OVERRIDE_CONDITION_INT_MAP.get(condition));
-                }
-            }
-            return TextUtils.join("&", conditionsStrings);
-        }
-
-        private static @Condition int getBitmaskFromString(@NonNull String str) {
-            if (TextUtils.isEmpty(str)) {
-                throw new IllegalArgumentException("Empty rule string");
-            }
-
-            String[] conditionStrings = str.trim().split("\\s*&\\s*");
-            int bitmask = 0;
-
-            for (String conditionStr : conditionStrings) {
-                if (!TextUtils.isEmpty(conditionStr)) {
-                    if (!OVERRIDE_CONDITION_STRING_MAP.containsKey(conditionStr)) {
-                        throw new IllegalArgumentException("Invalid conditions: " + str);
-                    }
-                    bitmask |= OVERRIDE_CONDITION_STRING_MAP.get(conditionStr);
-                }
-            }
-
-            return bitmask;
-        }
-
-        /**
-         * Check if provided conditions can meet all conditions in the rule.
-         *
-         * @param providedConditions The provided conditions
-         * @return {@code true} if all conditions are met.
-         */
-        boolean allMet(@Condition int providedConditions) {
-            return (providedConditions & mConditions) == mConditions;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            OverrideConditions that = (OverrideConditions) o;
-            return mConditions == that.mConditions;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(mConditions);
-        }
-
-        @Override
-        public String toString() {
-            return getStringFromBitmask(mConditions);
-        }
-    }
-
-    /**
-     * Constructor
-     *
-     * @param rules Data enabled override rules
-     */
-    public DataEnabledOverride(@NonNull String rules) {
-        updateRules(rules);
-    }
-
-    /**
-     * Update the data enabled override rules.
-     *
-     * @param newRules New override rules
-     */
-    @VisibleForTesting
-    public void updateRules(@NonNull String newRules) {
-        mRules.clear();
-        String[] rulesString = newRules.trim().split("\\s*,\\s*");
-        for (String rule : rulesString) {
-            if (!TextUtils.isEmpty(rule)) {
-                mRules.add(new OverrideRule(rule));
-            }
-        }
-    }
-
-    /**
-     * Set always allowing MMS
-     *
-     * @param allow {@code true} if always allowing, otherwise {@code false}.
-     */
-    public void setAlwaysAllowMms(boolean allow) {
-        if (allow) {
-            mRules.add(OVERRIDE_RULE_ALWAYS_ALLOW_MMS);
-        } else {
-            mRules.remove(OVERRIDE_RULE_ALWAYS_ALLOW_MMS);
-        }
-    }
-
-    /**
-     * Set allowing mobile data during voice call. This is used for allowing data on the non-default
-     * data SIM. When a voice call is placed on the non-default data SIM on DSDS devices, users will
-     * not be able to use mobile data. By calling this API, data will be temporarily enabled on the
-     * non-default data SIM during the life cycle of the voice call.
-     *
-     * @param allow {@code true} if allowing using data during voice call, {@code false} if
-     * disallowed.
-     */
-    public void setDataAllowedInVoiceCall(boolean allow) {
-        if (allow) {
-            mRules.add(OVERRIDE_RULE_ALLOW_DATA_DURING_VOICE_CALL);
-        } else {
-            mRules.remove(OVERRIDE_RULE_ALLOW_DATA_DURING_VOICE_CALL);
-        }
-    }
-
-    /**
-     * Check if data is allowed during voice call.
-     *
-     * @return {@code true} if data is allowed during voice call.
-     */
-    public boolean isDataAllowedInVoiceCall() {
-        return mRules.contains(OVERRIDE_RULE_ALLOW_DATA_DURING_VOICE_CALL);
-    }
-
-    public boolean isMmsAlwaysAllowed() {
-        return mRules.contains(OVERRIDE_RULE_ALWAYS_ALLOW_MMS);
-    }
-
-    private boolean canSatisfyAnyRule(@ApnType int apnType,
-                                      @Condition int providedConditions) {
-        for (OverrideRule rule : mRules) {
-            if (rule.isSatisfiedByConditions(apnType, providedConditions)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private @Condition int getCurrentConditions(Phone phone) {
-        int conditions = 0;
-
-        if (phone != null) {
-            // Check if the device is on voice call
-            if (phone.getState() != PhoneConstants.State.IDLE) {
-                conditions |= OverrideConditions.CONDITION_IN_VOICE_CALL;
-            }
-
-            int defaultDataSubId = SubscriptionController.getInstance().getDefaultDataSubId();
-
-            if (phone.getSubId() != defaultDataSubId) {
-                conditions |= OverrideConditions.CONDITION_NON_DEFAULT;
-            }
-
-            if (defaultDataSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
-                int phoneId = SubscriptionController.getInstance().getPhoneId(defaultDataSubId);
-                try {
-                    Phone defaultDataPhone = PhoneFactory.getPhone(phoneId);
-                    if (defaultDataPhone != null && defaultDataPhone.isUserDataEnabled()) {
-                        conditions |= OverrideConditions.CONDITION_DEFAULT_DATA_ENABLED;
-                    }
-                } catch (IllegalStateException e) {
-                    //ignore the exception and do not add the condition
-                    Log.d("DataEnabledOverride", e.getMessage());
-                }
-            }
-
-            if (TelephonyManager.from(phone.getContext()).isMultiSimEnabled()) {
-                conditions |= OverrideConditions.CONDITION_DSDS_ENABLED;
-            }
-        }
-
-        return conditions;
-    }
-
-    /**
-     * Check for given APN type if we should enable data.
-     *
-     * @param phone Phone object
-     * @param apnType APN type
-     * @return {@code true} if data should be enabled for the current condition.
-     */
-    public boolean shouldOverrideDataEnabledSettings(Phone phone, @ApnType int apnType) {
-        return canSatisfyAnyRule(apnType, getCurrentConditions(phone));
-    }
-
-    /**
-     * Get data enabled override rules.
-     *
-     * @return Get data enabled override rules in string format
-     */
-    @NonNull
-    public String getRules() {
-        List<String> ruleStrings = new ArrayList<>();
-        for (OverrideRule rule : mRules) {
-            ruleStrings.add(rule.toString());
-        }
-        return TextUtils.join(",", ruleStrings);
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        DataEnabledOverride that = (DataEnabledOverride) o;
-        return mRules.equals(that.mRules);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mRules);
-    }
-
-    @Override
-    public String toString() {
-        return "DataEnabledOverride: [rules=\"" + getRules() + "\"]";
-    }
-}
diff --git a/src/java/com/android/internal/telephony/data/DataEvaluation.java b/src/java/com/android/internal/telephony/data/DataEvaluation.java
index c061e16..2ba3fe4 100644
--- a/src/java/com/android/internal/telephony/data/DataEvaluation.java
+++ b/src/java/com/android/internal/telephony/data/DataEvaluation.java
@@ -195,50 +195,78 @@
     @VisibleForTesting
     public enum DataEvaluationReason {
         /** New request from the apps. */
-        NEW_REQUEST,
+        NEW_REQUEST(false),
         /** Data config changed. */
-        DATA_CONFIG_CHANGED,
+        DATA_CONFIG_CHANGED(true),
         /** SIM is loaded. */
-        SIM_LOADED,
+        SIM_LOADED(true),
         /** SIM is removed. */
-        SIM_REMOVAL,
+        SIM_REMOVAL(true),
         /** Data profiles changed. */
-        DATA_PROFILES_CHANGED,
+        DATA_PROFILES_CHANGED(true),
         /** When service state changes.(For now only considering data RAT and data registration). */
-        DATA_SERVICE_STATE_CHANGED,
+        DATA_SERVICE_STATE_CHANGED(true),
         /** When data is enabled or disabled (by user, carrier, thermal, etc...) */
-        DATA_ENABLED_CHANGED,
+        DATA_ENABLED_CHANGED(true),
         /** When data enabled overrides are changed (MMS always allowed, data on non-DDS sub). */
-        DATA_ENABLED_OVERRIDE_CHANGED,
+        DATA_ENABLED_OVERRIDE_CHANGED(true),
         /** When data roaming is enabled or disabled. */
-        ROAMING_ENABLED_CHANGED,
+        ROAMING_ENABLED_CHANGED(true),
         /** When voice call ended (for concurrent voice/data not supported RAT). */
-        VOICE_CALL_ENDED,
+        VOICE_CALL_ENDED(true),
         /** When network restricts or no longer restricts mobile data. */
-        DATA_RESTRICTED_CHANGED,
+        DATA_RESTRICTED_CHANGED(true),
         /** Network capabilities changed. The unsatisfied requests might have chances to attach. */
-        DATA_NETWORK_CAPABILITIES_CHANGED,
+        DATA_NETWORK_CAPABILITIES_CHANGED(true),
         /** When emergency call started or ended. */
-        EMERGENCY_CALL_CHANGED,
+        EMERGENCY_CALL_CHANGED(true),
         /** When data disconnected, re-evaluate later to see if data could be brought up again. */
-        RETRY_AFTER_DISCONNECTED,
+        RETRY_AFTER_DISCONNECTED(true),
         /** Data setup retry. */
-        DATA_RETRY,
+        DATA_RETRY(false),
         /** For handover evaluation, or for network tearing down after handover succeeds/fails. */
-        DATA_HANDOVER,
+        DATA_HANDOVER(true),
         /** Preferred transport changed. */
-        PREFERRED_TRANSPORT_CHANGED,
+        PREFERRED_TRANSPORT_CHANGED(true),
         /** Slice config changed. */
-        SLICE_CONFIG_CHANGED,
+        SLICE_CONFIG_CHANGED(true),
+        /** SRVCC state changed. */
+        SRVCC_STATE_CHANGED(true),
         /**
          * Single data network arbitration. On certain RATs, only one data network is allowed at the
          * same time.
          */
-        SINGLE_DATA_NETWORK_ARBITRATION,
+        SINGLE_DATA_NETWORK_ARBITRATION(true),
         /** Query from {@link TelephonyManager#isDataConnectivityPossible()}. */
-        EXTERNAL_QUERY,
+        EXTERNAL_QUERY(false),
         /** Tracking area code changed. */
-        TAC_CHANGED,
+        TAC_CHANGED(true),
+        /** Unsatisfied network request detached. */
+        UNSATISFIED_REQUEST_DETACHED(true);
+
+        /**
+         * {@code true} if the evaluation is due to environmental changes (i.e. SIM removal,
+         * registration state changes, etc....
+         */
+        private final boolean mIsConditionBased;
+
+        /**
+         * @return {@code true} if the evaluation is due to environmental changes (i.e. SIM removal,
+         * registration state changes, etc....
+         */
+        public boolean isConditionBased() {
+            return mIsConditionBased;
+        }
+
+        /**
+         * Constructor
+         *
+         * @param isConditionBased {@code true} if the evaluation is due to environmental changes
+         * (i.e. SIM removal, registration state changes, etc....)
+         */
+        DataEvaluationReason(boolean isConditionBased) {
+            mIsConditionBased = isConditionBased;
+        }
     }
 
     /** Disallowed reasons. There could be multiple reasons if it is not allowed. */
@@ -295,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;
 
@@ -334,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 e483d2b..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;
@@ -87,6 +90,7 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.RIL;
+import com.android.internal.telephony.data.DataConfigManager.DataConfigManagerCallback;
 import com.android.internal.telephony.data.DataEvaluation.DataAllowedReason;
 import com.android.internal.telephony.data.DataNetworkController.NetworkRequestList;
 import com.android.internal.telephony.data.DataRetryManager.DataHandoverRetryEntry;
@@ -165,6 +169,9 @@
     /** Event for detaching a network request. */
     private static final int EVENT_DETACH_NETWORK_REQUEST = 3;
 
+    /** Event when detect radio not available. */
+    private static final int  EVENT_RADIO_NOT_AVAILABLE = 4;
+
     /** Event for allocating PDU session id response. */
     private static final int EVENT_ALLOCATE_PDU_SESSION_ID_RESPONSE = 5;
 
@@ -189,9 +196,6 @@
     /** Event for display info changed. This is for getting 5G NSA or mmwave information. */
     private static final int EVENT_DISPLAY_INFO_CHANGED = 13;
 
-    /** Event for initiating an handover between cellular and IWLAN. */
-    private static final int EVENT_START_HANDOVER = 14;
-
     /** Event for setup data call (for handover) response from the data service. */
     private static final int EVENT_HANDOVER_RESPONSE = 15;
 
@@ -231,6 +235,24 @@
     /** Event for CSS indicator changed. */
     private static final int EVENT_CSS_INDICATOR_CHANGED = 24;
 
+    /**
+     * Event for notifying source transport that handover is about to be initiated on target
+     * transport.
+     */
+    private static final int EVENT_NOTIFY_HANDOVER_STARTED = 25;
+
+    /**
+     * Event for the response of notifying source transport that handover is about to be initiated
+     * on target transport.
+     */
+    private static final int EVENT_NOTIFY_HANDOVER_STARTED_RESPONSE = 26;
+
+    /**
+     * Event for the response of notifying source transport that handover is cancelled/failed on the
+     * target transport.
+     */
+    private static final int EVENT_NOTIFY_HANDOVER_CANCELLED_RESPONSE = 27;
+
     /** Invalid context id. */
     private static final int INVALID_CID = -1;
 
@@ -245,8 +267,9 @@
     private static final int DEFAULT_INTERNET_NETWORK_SCORE = 50;
     private static final int OTHER_NETWORK_SCORE = 45;
 
-    @IntDef(prefix = {"DEACTIVATION_REASON_"},
+    @IntDef(prefix = {"TEAR_DOWN_REASON_"},
             value = {
+                    TEAR_DOWN_REASON_NONE,
                     TEAR_DOWN_REASON_CONNECTIVITY_SERVICE_UNWANTED,
                     TEAR_DOWN_REASON_SIM_REMOVAL,
                     TEAR_DOWN_REASON_AIRPLANE_MODE_ON,
@@ -279,6 +302,9 @@
             })
     public @interface TearDownReason {}
 
+    /** Data network was not torn down. */
+    public static final int TEAR_DOWN_REASON_NONE = 0;
+
     /** Data network tear down requested by connectivity service. */
     public static final int TEAR_DOWN_REASON_CONNECTIVITY_SERVICE_UNWANTED = 1;
 
@@ -356,7 +382,7 @@
     /** Data network tear down due to data profile not preferred. */
     public static final int TEAR_DOWN_REASON_DATA_PROFILE_NOT_PREFERRED = 26;
 
-    /** Data network tear down due to not allowed by policy. */
+    /** Data network tear down due to handover not allowed by policy. */
     public static final int TEAR_DOWN_REASON_NOT_ALLOWED_BY_POLICY = 27;
 
     /** Data network tear down due to illegal state. */
@@ -526,6 +552,10 @@
     /** Data network controller. */
     private final @NonNull DataNetworkController mDataNetworkController;
 
+    /** Data network controller callback. */
+    private final @NonNull DataNetworkController.DataNetworkControllerCallback
+            mDataNetworkControllerCallback;
+
     /** Data config manager. */
     private final @NonNull DataConfigManager mDataConfigManager;
 
@@ -605,6 +635,11 @@
     private @DataFailureCause int mFailCause = DataFailCause.NONE;
 
     /**
+     * The tear down reason if the data call is voluntarily deactivated, not due to failure.
+     */
+    private @TearDownReason int mTearDownReason = TEAR_DOWN_REASON_NONE;
+
+    /**
      * The retry delay in milliseconds from setup data failure.
      */
     private long mRetryDelayMillis = DataCallResponse.RETRY_DURATION_UNDEFINED;
@@ -624,6 +659,11 @@
      */
     private @TransportType int mTransport;
 
+    /**
+     * The last known data network type.
+     */
+    private @NetworkType int mLastKnownDataNetworkType;
+
     /** The reason that why setting up this data network is allowed. */
     private @NonNull DataAllowedReason mDataAllowedReason;
 
@@ -641,6 +681,9 @@
      */
     private @NonNull int[] mAdministratorUids = new int[0];
 
+    /** Carrier privileges callback to monitor administrator UID change. */
+    private @Nullable TelephonyManager.CarrierPrivilegesCallback mCarrierPrivilegesCallback;
+
     /**
      * Carrier service package uid. This UID will not change through the life cycle of data network.
      */
@@ -652,6 +695,11 @@
     private @Nullable LinkBandwidthEstimatorCallback mLinkBandwidthEstimatorCallback;
 
     /**
+     * Data config callback for carrier config update.
+     */
+    private @Nullable DataConfigManagerCallback mDataConfigManagerCallback;
+
+    /**
      * The network bandwidth.
      */
     public static class NetworkBandwidth {
@@ -754,9 +802,10 @@
          *
          * @param dataNetwork The data network.
          * @param cause The disconnect cause.
+         * @param tearDownReason The reason the network was torn down
          */
         public abstract void onDisconnected(@NonNull DataNetwork dataNetwork,
-                @DataFailureCause int cause);
+                @DataFailureCause int cause, @TearDownReason int tearDownReason);
 
         /**
          * Called when handover between IWLAN and cellular network succeeded.
@@ -810,6 +859,14 @@
          * @param dataNetwork The data network.
          */
         public abstract void onTrackNetworkUnwanted(@NonNull DataNetwork dataNetwork);
+
+        /**
+         * Called when a network request is detached after no longer satisfied.
+         *
+         * @param networkRequest The detached network request.
+         */
+        public abstract void onRetryUnsatisfiedNetworkRequest(
+                @NonNull TelephonyNetworkRequest networkRequest);
     }
 
     /**
@@ -846,12 +903,14 @@
         mAccessNetworksManager = phone.getAccessNetworksManager();
         mVcnManager = mPhone.getContext().getSystemService(VcnManager.class);
         mDataNetworkController = phone.getDataNetworkController();
+        mDataNetworkControllerCallback = new DataNetworkController.DataNetworkControllerCallback(
+                getHandler()::post) {
+            @Override
+            public void onSubscriptionPlanOverride() {
+                sendMessage(EVENT_SUBSCRIPTION_PLAN_OVERRIDE);
+            }};
         mDataNetworkController.registerDataNetworkControllerCallback(
-                new DataNetworkController.DataNetworkControllerCallback(getHandler()::post) {
-                    @Override
-                    public void onSubscriptionPlanOverride() {
-                        sendMessage(EVENT_SUBSCRIPTION_PLAN_OVERRIDE);
-                    }});
+                mDataNetworkControllerCallback);
         mDataConfigManager = mDataNetworkController.getDataConfigManager();
         mDataCallSessionStats = new DataCallSessionStats(mPhone);
         mDataNetworkCallback = callback;
@@ -863,13 +922,15 @@
             mTrafficDescriptors.add(dataProfile.getTrafficDescriptor());
         }
         mTransport = transport;
+        mLastKnownDataNetworkType = getDataNetworkType();
         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);
-        mTcpBufferSizes = mDataConfigManager.getDefaultTcpConfigString();
+        for (int transportType : mAccessNetworksManager.getAvailableTransports()) {
+            mCid.put(transportType, INVALID_CID);
+        }
         mTelephonyDisplayInfo = mPhone.getDisplayInfoController().getTelephonyDisplayInfo();
+        mTcpBufferSizes = mDataConfigManager.getTcpConfigString(mTelephonyDisplayInfo);
 
         for (TelephonyNetworkRequest networkRequest : networkRequestList) {
             networkRequest.setAttachedNetwork(DataNetwork.this);
@@ -982,18 +1043,39 @@
         @Override
         public void enter() {
             logv("Registering all events.");
-            mDataConfigManager.registerForConfigUpdate(getHandler(), EVENT_DATA_CONFIG_UPDATED);
+            mDataConfigManagerCallback = new DataConfigManagerCallback(getHandler()::post) {
+                @Override
+                public void onCarrierConfigChanged() {
+                    sendMessage(EVENT_DATA_CONFIG_UPDATED);
+                }
+            };
             mRil.registerForPcoData(getHandler(), EVENT_PCO_DATA_RECEIVED, null);
+
+            mDataConfigManager.registerCallback(mDataConfigManagerCallback);
             mPhone.getDisplayInfoController().registerForTelephonyDisplayInfoChanged(
                     getHandler(), EVENT_DISPLAY_INFO_CHANGED, null);
             mPhone.getServiceStateTracker().registerForServiceStateChanged(getHandler(),
-                    EVENT_SERVICE_STATE_CHANGED);
+                    EVENT_SERVICE_STATE_CHANGED, null);
             for (int transport : mAccessNetworksManager.getAvailableTransports()) {
                 mDataServiceManagers.get(transport)
                         .registerForDataCallListChanged(getHandler(), EVENT_DATA_STATE_CHANGED);
             }
-            mPhone.getCarrierPrivilegesTracker().registerCarrierPrivilegesListener(getHandler(),
-                    EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED, null);
+
+            mCarrierPrivilegesCallback =
+                    (Set<String> privilegedPackageNames, Set<Integer> privilegedUids) -> {
+                        log("onCarrierPrivilegesChanged, Uids=" + privilegedUids.toString());
+                        Message message = obtainMessage(EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED);
+                        AsyncResult.forMessage(
+                                message,
+                                privilegedUids.stream().mapToInt(i -> i).toArray(),
+                                null /* ex */);
+                        sendMessage(message);
+                    };
+            TelephonyManager tm = mPhone.getContext().getSystemService(TelephonyManager.class);
+            if (tm != null) {
+                tm.registerCarrierPrivilegesCallback(
+                        mPhone.getPhoneId(), getHandler()::post, mCarrierPrivilegesCallback);
+            }
 
             mPhone.getServiceStateTracker().registerForCssIndicatorChanged(
                     getHandler(), EVENT_CSS_INDICATOR_CHANGED, null);
@@ -1027,7 +1109,10 @@
             mPhone.getCallTracker().unregisterForVoiceCallEnded(getHandler());
 
             mPhone.getServiceStateTracker().unregisterForCssIndicatorChanged(getHandler());
-            mPhone.getCarrierPrivilegesTracker().unregisterCarrierPrivilegesListener(getHandler());
+            TelephonyManager tm = mPhone.getContext().getSystemService(TelephonyManager.class);
+            if (tm != null && mCarrierPrivilegesCallback != null) {
+                tm.unregisterCarrierPrivilegesCallback(mCarrierPrivilegesCallback);
+            }
             for (int transport : mAccessNetworksManager.getAvailableTransports()) {
                 mDataServiceManagers.get(transport)
                         .unregisterForDataCallListChanged(getHandler());
@@ -1035,18 +1120,22 @@
             mPhone.getServiceStateTracker().unregisterForServiceStateChanged(getHandler());
             mPhone.getDisplayInfoController().unregisterForTelephonyDisplayInfoChanged(
                     getHandler());
-            mDataConfigManager.unregisterForConfigUpdate(getHandler());
             mRil.unregisterForPcoData(getHandler());
+            mDataConfigManager.unregisterCallback(mDataConfigManagerCallback);
         }
 
         @Override
         public boolean processMessage(Message msg) {
             switch (msg.what) {
                 case EVENT_DATA_CONFIG_UPDATED:
-                    onDataConfigUpdated();
+                    onCarrierConfigUpdated();
                     break;
                 case EVENT_SERVICE_STATE_CHANGED: {
-                    mDataCallSessionStats.onDrsOrRatChanged(getDataNetworkType());
+                    int networkType = getDataNetworkType();
+                    mDataCallSessionStats.onDrsOrRatChanged(networkType);
+                    if (networkType != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
+                        mLastKnownDataNetworkType = networkType;
+                    }
                     updateSuspendState();
                     updateNetworkCapabilities();
                     break;
@@ -1057,7 +1146,8 @@
                     break;
                 }
                 case EVENT_DETACH_NETWORK_REQUEST: {
-                    onDetachNetworkRequest((TelephonyNetworkRequest) msg.obj);
+                    onDetachNetworkRequest((TelephonyNetworkRequest) msg.obj,
+                            msg.arg1 != 0 /* shouldRetry */);
                     updateNetworkScore();
                     break;
                 }
@@ -1088,6 +1178,9 @@
                     onPcoDataReceived((PcoData) ar.result);
                     break;
                 }
+                case EVENT_NOTIFY_HANDOVER_CANCELLED_RESPONSE:
+                    log("Notified handover cancelled.");
+                    break;
                 case EVENT_BANDWIDTH_ESTIMATE_FROM_MODEM_CHANGED:
                 case EVENT_TEAR_DOWN_NETWORK:
                 case EVENT_STUCK_IN_TRANSIENT_STATE:
@@ -1099,10 +1192,23 @@
                     // Ignore the events when not in the correct state.
                     log("Ignored " + eventToString(msg.what));
                     break;
-                case EVENT_START_HANDOVER:
-                    log("Ignore the handover to " + AccessNetworkConstants
+                case EVENT_NOTIFY_HANDOVER_STARTED_RESPONSE:
+                case EVENT_NOTIFY_HANDOVER_STARTED:
+                    // We reach here if network is not in the right state.
+                    if (msg.obj != null) {
+                        // Cancel it because it's either HO in progress or will soon disconnect.
+                        // Either case we want to clean up obsolete retry attempts.
+                        DataHandoverRetryEntry retryEntry = (DataHandoverRetryEntry) msg.obj;
+                        retryEntry.setState(DataRetryEntry.RETRY_STATE_CANCELLED);
+                    }
+                    log("Ignore handover to " + AccessNetworkConstants
                             .transportTypeToString(msg.arg1) + " request.");
                     break;
+                case EVENT_RADIO_NOT_AVAILABLE:
+                    mFailCause = DataFailCause.RADIO_NOT_AVAILABLE;
+                    loge(eventToString(msg.what) + ": transition to disconnected state");
+                    transitionTo(mDisconnectedState);
+                    break;
                 default:
                     loge("Unhandled event " + eventToString(msg.what));
                     break;
@@ -1166,7 +1272,7 @@
                             msg.getData().getParcelable(DataServiceManager.DATA_CALL_RESPONSE);
                     onSetupResponse(resultCode, dataCallResponse);
                     break;
-                case EVENT_START_HANDOVER:
+                case EVENT_NOTIFY_HANDOVER_STARTED:
                 case EVENT_TEAR_DOWN_NETWORK:
                 case EVENT_WAITING_FOR_TEARING_DOWN_CONDITION_MET:
                     // Defer the request until connected or disconnected.
@@ -1234,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();
         }
@@ -1256,17 +1363,6 @@
                     }
 
                     int tearDownReason = msg.arg1;
-                    // If the tear down request is from upper layer, for example, IMS service
-                    // releases network request, we don't need to delay. The purpose of the delay
-                    // is to have IMS service have time to perform IMS de-registration, so if this
-                    // request is from IMS service itself, that means IMS service is already aware
-                    // of the tear down. So there is no need to delay in this case.
-                    if (tearDownReason != TEAR_DOWN_REASON_CONNECTIVITY_SERVICE_UNWANTED
-                            && shouldDelayImsTearDown()) {
-                        logl("Delay IMS tear down until call ends. reason="
-                                + tearDownReasonToString(tearDownReason));
-                        break;
-                    }
 
                     removeMessages(EVENT_TEAR_DOWN_NETWORK);
                     removeDeferredMessages(EVENT_TEAR_DOWN_NETWORK);
@@ -1285,8 +1381,21 @@
                 case EVENT_DISPLAY_INFO_CHANGED:
                     onDisplayInfoChanged();
                     break;
-                case EVENT_START_HANDOVER:
-                    onStartHandover(msg.arg1, (DataHandoverRetryEntry) msg.obj);
+                case EVENT_NOTIFY_HANDOVER_STARTED:
+                    // Notify source transport that handover is about to start. Note this will not
+                    // initiate the handover process on target transport, but more for notifying
+                    // the source transport so that PDU session id can be preserved if network
+                    // notifies PDN lost during handover. The real handover process will kick off
+                    // after receiving EVENT_NOTIFY_HANDOVER_STARTED_RESPONSE.
+                    log("Notifying source transport "
+                            + AccessNetworkConstants.transportTypeToString(mTransport)
+                            + " that handover is about to start.");
+                    mDataServiceManagers.get(mTransport).startHandover(mCid.get(mTransport),
+                            obtainMessage(EVENT_NOTIFY_HANDOVER_STARTED_RESPONSE, 0, msg.arg2,
+                                    msg.obj));
+                    // We enter handover state here because this is the first action we do for
+                    // handover.
+                    transitionTo(mHandoverState);
                     break;
                 case EVENT_SUBSCRIPTION_PLAN_OVERRIDE:
                     updateMeteredAndCongested();
@@ -1339,8 +1448,15 @@
                     // Otherwise the deferred message might be incorrectly treated as "disconnected"
                     // signal. So we only defer the related data call list changed event, and drop
                     // the unrelated.
-                    if (shouldDeferDataStateChangedEvent(msg)) {
-                        log("Defer message " + eventToString(msg.what));
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    int transport = (int) ar.userObj;
+                    List<DataCallResponse> responseList = (List<DataCallResponse>) ar.result;
+                    if (transport != mTransport) {
+                        log("Dropped unrelated "
+                                + AccessNetworkConstants.transportTypeToString(transport)
+                                + " data call list changed event. " + responseList);
+                    } else {
+                        log("Defer message " + eventToString(msg.what) + ":" + responseList);
                         deferMessage(msg);
                     }
                     break;
@@ -1354,6 +1470,9 @@
                     log("Defer message " + eventToString(msg.what));
                     deferMessage(msg);
                     break;
+                case EVENT_NOTIFY_HANDOVER_STARTED_RESPONSE:
+                    onStartHandover(msg.arg2, (DataHandoverRetryEntry) msg.obj);
+                    break;
                 case EVENT_HANDOVER_RESPONSE:
                     int resultCode = msg.arg1;
                     DataCallResponse dataCallResponse =
@@ -1362,11 +1481,11 @@
                             (DataHandoverRetryEntry) msg.obj);
                     break;
                 case EVENT_STUCK_IN_TRANSIENT_STATE:
+                    // enable detection only for valid timeout range
                     reportAnomaly("Data service did not respond the handover request within "
-                            + TimeUnit.MILLISECONDS.toSeconds(
+                                    + TimeUnit.MILLISECONDS.toSeconds(
                             mDataConfigManager.getNetworkHandoverTimeoutMs()) + " seconds.",
                             "1afe68cb-8b41-4964-a737-4f34372429ea");
-
                     // Handover failed. Use the retry logic defined in
                     // CarrierConfigManager.KEY_TELEPHONY_DATA_HANDOVER_RETRY_RULES_STRING_ARRAY.
                     long retry = DataCallResponse.RETRY_DURATION_UNDEFINED;
@@ -1384,39 +1503,6 @@
             }
             return HANDLED;
         }
-
-        /**
-         * Check if the data call list changed event should be deferred or dropped when handover
-         * is in progress.
-         *
-         * @param msg The data call list changed message.
-         *
-         * @return {@code true} if the message should be deferred.
-         */
-        private boolean shouldDeferDataStateChangedEvent(@NonNull Message msg) {
-            // The data call list changed event should be conditionally deferred.
-            // Otherwise the deferred message might be incorrectly treated as "disconnected"
-            // signal. So we only defer the related data call list changed event, and drop
-            // the unrelated.
-            AsyncResult ar = (AsyncResult) msg.obj;
-            int transport = (int) ar.userObj;
-            List<DataCallResponse> responseList = (List<DataCallResponse>) ar.result;
-            if (transport != mTransport) {
-                log("Dropped unrelated " + AccessNetworkConstants.transportTypeToString(transport)
-                        + " data call list changed event. " + responseList);
-                return false;
-            }
-
-            // Check if the data call list changed event are related to the current data network.
-            boolean related = responseList.stream().anyMatch(
-                    r -> mCid.get(mTransport) == r.getId());
-            if (related) {
-                log("Deferred the related data call list changed event." + responseList);
-            } else {
-                log("Dropped unrelated data call list changed event. " + responseList);
-            }
-            return related;
-        }
     }
 
     /**
@@ -1460,6 +1546,7 @@
                 case EVENT_STUCK_IN_TRANSIENT_STATE:
                     // After frameworks issues deactivate data call request, RIL should report
                     // data disconnected through data call list changed event subsequently.
+
                     reportAnomaly("RIL did not send data call list changed event after "
                             + "deactivate data call request within "
                             + TimeUnit.MILLISECONDS.toSeconds(
@@ -1512,8 +1599,11 @@
             //************************************************************//
 
             if (mEverConnected) {
+                mLinkStatus = DataCallResponse.LINK_STATUS_INACTIVE;
                 mDataNetworkCallback.invokeFromExecutor(() -> mDataNetworkCallback
-                        .onDisconnected(DataNetwork.this, mFailCause));
+                        .onLinkStatusChanged(DataNetwork.this, mLinkStatus));
+                mDataNetworkCallback.invokeFromExecutor(() -> mDataNetworkCallback
+                        .onDisconnected(DataNetwork.this, mFailCause, mTearDownReason));
                 if (mTransport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
                     unregisterForWwanEvents();
                 }
@@ -1524,6 +1614,8 @@
             }
             notifyPreciseDataConnectionState();
             mNetworkAgent.unregister();
+            mDataNetworkController.unregisterDataNetworkControllerCallback(
+                    mDataNetworkControllerCallback);
             mDataCallSessionStats.onDataCallDisconnected(mFailCause);
 
             if (mTransport == AccessNetworkConstants.TRANSPORT_TYPE_WLAN
@@ -1549,6 +1641,7 @@
     private void registerForWwanEvents() {
         registerForBandwidthUpdate();
         mKeepaliveTracker.registerForKeepaliveStatus();
+        mRil.registerForNotAvailable(this.getHandler(), EVENT_RADIO_NOT_AVAILABLE, null);
     }
 
     /**
@@ -1557,6 +1650,7 @@
     private void unregisterForWwanEvents() {
         unregisterForBandwidthUpdate();
         mKeepaliveTracker.unregisterForKeepaliveStatus();
+        mRil.unregisterForNotAvailable(this.getHandler());
     }
 
     @Override
@@ -1623,12 +1717,21 @@
      * Called when detaching the network request from this data network.
      *
      * @param networkRequest Network request to detach.
+     * @param shouldRetry {@code true} if the detached network request should be retried.
      */
-    private void onDetachNetworkRequest(@NonNull TelephonyNetworkRequest networkRequest) {
+    private void onDetachNetworkRequest(@NonNull TelephonyNetworkRequest networkRequest,
+            boolean shouldRetry) {
         mAttachedNetworkRequestList.remove(networkRequest);
         networkRequest.setState(TelephonyNetworkRequest.REQUEST_STATE_UNSATISFIED);
         networkRequest.setAttachedNetwork(null);
 
+        if (shouldRetry) {
+            // Inform DataNetworkController that a network request was detached and should be
+            // scheduled to retry.
+            mDataNetworkCallback.invokeFromExecutor(
+                    () -> mDataNetworkCallback.onRetryUnsatisfiedNetworkRequest(networkRequest));
+        }
+
         if (mAttachedNetworkRequestList.isEmpty()) {
             log("All network requests are detached.");
 
@@ -1649,12 +1752,15 @@
      * network.
      *
      * @param networkRequest Network request to detach.
+     * @param shouldRetry {@code true} if the detached network request should be retried.
      */
-    public void detachNetworkRequest(@NonNull TelephonyNetworkRequest networkRequest) {
+    public void detachNetworkRequest(@NonNull TelephonyNetworkRequest networkRequest,
+            boolean shouldRetry) {
         if (getCurrentState() == null || isDisconnected()) {
             return;
         }
-        sendMessage(obtainMessage(EVENT_DETACH_NETWORK_REQUEST, networkRequest));
+        sendMessage(obtainMessage(EVENT_DETACH_NETWORK_REQUEST, shouldRetry ? 1 : 0, 0,
+                networkRequest));
     }
 
     /**
@@ -1705,13 +1811,15 @@
 
     /**
      * Remove network requests that can't be satisfied anymore.
+     *
+     * @param shouldRetry {@code true} if the detached network requests should be retried.
      */
-    private void removeUnsatisfiedNetworkRequests() {
+    private void removeUnsatisfiedNetworkRequests(boolean shouldRetry) {
         for (TelephonyNetworkRequest networkRequest : mAttachedNetworkRequestList) {
             if (!networkRequest.canBeSatisfiedBy(mNetworkCapabilities)) {
                 log("removeUnsatisfiedNetworkRequests: " + networkRequest
                         + " can't be satisfied anymore. Will be detached.");
-                detachNetworkRequest(networkRequest);
+                detachNetworkRequest(networkRequest, shouldRetry);
             }
         }
     }
@@ -1780,6 +1888,31 @@
     }
 
     /**
+     * In some rare cases we need to re-create the network agent, for example, underlying network
+     * IP changed, or when we unfortunately need to remove/add a immutable network capability.
+     */
+    private void recreateNetworkAgent() {
+        if (isConnecting() || isDisconnected() || isDisconnecting()) {
+            loge("Incorrect state for re-creating the network agent.");
+            return;
+        }
+
+        // Abandon the network agent because we are going to create a new one.
+        mNetworkAgent.abandon();
+        // Create a new network agent and register with connectivity service. Note that the agent
+        // will always be registered with NOT_SUSPENDED capability.
+        mNetworkAgent = createNetworkAgent();
+        mNetworkAgent.markConnected();
+        // Because network agent is always created with NOT_SUSPENDED, we need to update
+        // the suspended if it's was in suspended state.
+        if (mSuspended) {
+            log("recreateNetworkAgent: The network is in suspended state. Update the network"
+                    + " capability again. nc=" + mNetworkCapabilities);
+            mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
+        }
+    }
+
+    /**
      * Update the network capabilities.
      */
     private void updateNetworkCapabilities() {
@@ -1821,7 +1954,8 @@
                         DataSpecificRegistrationInfo dsri = nri.getDataSpecificInfo();
                         // Check if the network is non-VoPS.
                         if (dsri != null && dsri.getVopsSupportInfo() != null
-                                && !dsri.getVopsSupportInfo().isVopsSupported()) {
+                                && !dsri.getVopsSupportInfo().isVopsSupported()
+                                && !mDataConfigManager.shouldKeepNetworkUpInNonVops()) {
                             builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL);
                         }
                         log("updateNetworkCapabilities: dsri=" + dsri);
@@ -1977,13 +2111,8 @@
                 logl("updateNetworkCapabilities: Immutable capabilities changed. Re-create the "
                         + "network agent. Attempted to change from " + mNetworkCapabilities + " to "
                         + nc);
-                // Abandon the network agent because we are going to create a new one.
-                mNetworkAgent.abandon();
-                // Update the capabilities first so the new network agent would be created with the
-                // new capabilities.
                 mNetworkCapabilities = nc;
-                mNetworkAgent = createNetworkAgent();
-                mNetworkAgent.markConnected();
+                recreateNetworkAgent();
             } else {
                 // Now we need to inform connectivity service and data network controller
                 // about the capabilities changed.
@@ -1992,7 +2121,11 @@
                 mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
             }
 
-            removeUnsatisfiedNetworkRequests();
+            // Only retry the request when the network is in connected or handover state. This is to
+            // prevent request is detached during connecting state, and then become a setup/detach
+            // infinite loop.
+            boolean shouldRetry = isConnected() || isHandoverInProgress();
+            removeUnsatisfiedNetworkRequests(shouldRetry);
             mDataNetworkCallback.invokeFromExecutor(() -> mDataNetworkCallback
                     .onNetworkCapabilitiesChanged(DataNetwork.this));
         } else {
@@ -2173,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
@@ -2272,20 +2410,13 @@
             // If the new link properties is not compatible (e.g. IP changes, interface changes),
             // then we should de-register the network agent and re-create a new one.
             if ((isConnected() || isHandoverInProgress())
-                    && !isLinkPropertiesCompatible(linkProperties, mLinkProperties)) {
+                    && !isLinkPropertiesCompatible(mLinkProperties, linkProperties)) {
                 logl("updateDataNetwork: Incompatible link properties detected. Re-create the "
                         + "network agent. Changed from " + mLinkProperties + " to "
                         + linkProperties);
 
                 mLinkProperties = linkProperties;
-
-                // Abandon the network agent because we are going to create a new one.
-                mNetworkAgent.abandon();
-                // Update the link properties first so the new network agent would be created with
-                // the new link properties.
-                mLinkProperties = linkProperties;
-                mNetworkAgent = createNetworkAgent();
-                mNetworkAgent.markConnected();
+                recreateNetworkAgent();
             } else {
                 mLinkProperties = linkProperties;
                 log("sendLinkProperties " + mLinkProperties);
@@ -2307,14 +2438,16 @@
         logl("onSetupResponse: resultCode=" + DataServiceCallback.resultCodeToString(resultCode)
                 + ", response=" + response);
         mFailCause = getFailCauseFromDataCallResponse(resultCode, response);
-        validateDataCallResponse(response);
+        validateDataCallResponse(response, true /*isSetupResponse*/);
         if (mFailCause == DataFailCause.NONE) {
-            if (mDataNetworkController.isNetworkInterfaceExisting(response.getInterfaceName())) {
-                logl("Interface " + response.getInterfaceName() + " already existing. Silently "
-                        + "tear down now.");
+            DataNetwork dataNetwork = mDataNetworkController.getDataNetworkByInterface(
+                    response.getInterfaceName());
+            if (dataNetwork != null) {
+                logl("Interface " + response.getInterfaceName() + " has been already used by "
+                        + dataNetwork + ". Silently tear down now.");
                 // If this is a pre-5G data setup, that means APN database has some problems. For
                 // example, different APN settings have the same APN name.
-                if (response.getTrafficDescriptors().isEmpty()) {
+                if (response.getTrafficDescriptors().isEmpty() && dataNetwork.isConnected()) {
                     reportAnomaly("Duplicate network interface " + response.getInterfaceName()
                             + " detected.", "62f66e7e-8d71-45de-a57b-dc5c78223fd5");
                 }
@@ -2370,15 +2503,18 @@
                 getDataNetworkType(),
                 apnTypeBitmask,
                 protocol,
-                mFailCause);
+                // Log the raw fail cause to avoid large amount of UNKNOWN showing on metrics.
+                response != null ? response.getCause() : mFailCause);
     }
 
     /**
      * If the {@link DataCallResponse} contains invalid info, triggers an anomaly report.
      *
      * @param response The response to be validated
+     * @param isSetupResponse {@code true} if the response is for initial data call setup
      */
-    private void validateDataCallResponse(@Nullable DataCallResponse response) {
+    private void validateDataCallResponse(@Nullable DataCallResponse response,
+            boolean isSetupResponse) {
         if (response == null
                 || response.getLinkStatus() == DataCallResponse.LINK_STATUS_INACTIVE) return;
         int failCause = response.getCause();
@@ -2398,6 +2534,37 @@
                 reportAnomaly("Invalid DataCallResponse detected",
                         "1f273e9d-b09c-46eb-ad1c-421d01f61164");
             }
+            // Check IP for initial setup response
+            NetworkRegistrationInfo nri = getNetworkRegistrationInfo();
+            if (isSetupResponse
+                    && mDataProfile.getApnSetting() != null && nri != null && nri.isInService()) {
+                boolean isRoaming = nri.getNetworkRegistrationState()
+                        == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING;
+                int protocol = isRoaming ? mDataProfile.getApnSetting().getRoamingProtocol()
+                        : mDataProfile.getApnSetting().getProtocol();
+                String underlyingDataService = mTransport
+                        == AccessNetworkConstants.TRANSPORT_TYPE_WWAN
+                        ? "RIL" : "IWLAN data service";
+                if (protocol == ApnSetting.PROTOCOL_IP) {
+                    if (response.getAddresses().stream().anyMatch(
+                            la -> la.getAddress() instanceof java.net.Inet6Address)) {
+                        loge("Invalid DataCallResponse. Requested IPv4 but got IPv6 address. "
+                                + response);
+                        reportAnomaly(underlyingDataService + " reported mismatched IP "
+                                + "type. Requested IPv4 but got IPv6 address.",
+                                "7744f920-fb64-4db0-ba47-de0eae485a80");
+                    }
+                } else if (protocol == ApnSetting.PROTOCOL_IPV6) {
+                    if (response.getAddresses().stream().anyMatch(
+                            la -> la.getAddress() instanceof java.net.Inet4Address)) {
+                        loge("Invalid DataCallResponse. Requested IPv6 but got IPv4 address. "
+                                + response);
+                        reportAnomaly(underlyingDataService + " reported mismatched IP "
+                                        + "type. Requested IPv6 but got IPv4 address.",
+                                "7744f920-fb64-4db0-ba47-de0eae485a80");
+                    }
+                }
+            }
         } else if (!DataFailCause.isFailCauseExisting(failCause)) { // Setup data failed.
             loge("Invalid DataFailCause in " + response);
             reportAnomaly("Invalid DataFailCause: (0x" + Integer.toHexString(failCause)
@@ -2418,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);
         }
@@ -2435,6 +2602,7 @@
         if (getCurrentState() == null || isDisconnected()) {
             return;
         }
+        mTearDownReason = reason;
         sendMessage(obtainMessage(EVENT_TEAR_DOWN_NETWORK, reason));
     }
 
@@ -2454,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 shouldDelayImsTearDown() {
-        return mDataConfigManager.isImsDelayTearDownEnabled()
+    public boolean shouldDelayImsTearDownDueToInCall() {
+        return mDataConfigManager.isImsDelayTearDownUntilVoiceCallEndEnabled()
                 && mNetworkCapabilities != null
                 && mNetworkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL)
                 && mPhone.getImsPhone() != null
@@ -2529,7 +2698,7 @@
         if (response != null) {
             if (!response.equals(mDataCallResponse)) {
                 log("onDataStateChanged: " + response);
-                validateDataCallResponse(response);
+                validateDataCallResponse(response, false /*isSetupResponse*/);
                 mDataCallResponse = response;
                 if (response.getLinkStatus() != DataCallResponse.LINK_STATUS_INACTIVE) {
                     updateDataNetwork(response);
@@ -2557,10 +2726,10 @@
     }
 
     /**
-     * Called when data config updated.
+     * Called when carrier config updated.
      */
-    private void onDataConfigUpdated() {
-        log("onDataConfigUpdated");
+    private void onCarrierConfigUpdated() {
+        log("onCarrierConfigUpdated");
 
         updateBandwidthFromDataConfig();
         updateTcpBufferSizes();
@@ -2686,9 +2855,12 @@
             log("updateMeteredAndCongested: mTempNotMeteredSupported changed to "
                     + mTempNotMeteredSupported);
         }
-        if ((mDataNetworkController.getUnmeteredOverrideNetworkTypes().contains(networkType)
-                || isNetworkTypeUnmetered(networkType)) != mTempNotMetered) {
-            mTempNotMetered = !mTempNotMetered;
+        boolean isTempNotMetered = mDataConfigManager.isNetworkTypeUnmetered(
+                mTelephonyDisplayInfo, mPhone.getServiceState())
+                && (mDataNetworkController.getUnmeteredOverrideNetworkTypes().contains(networkType)
+                || isNetworkTypeUnmetered(networkType));
+        if (isTempNotMetered != mTempNotMetered) {
+            mTempNotMetered = isTempNotMetered;
             changed = true;
             log("updateMeteredAndCongested: mTempNotMetered changed to " + mTempNotMetered);
         }
@@ -2701,6 +2873,10 @@
         if (changed) {
             updateNetworkCapabilities();
         }
+        if (mTempNotMetered && isInternetSupported()) {
+            // NR NSA and NR have the same network type: NR
+            mDataCallSessionStats.onUnmeteredUpdate(networkType);
+        }
     }
 
     /**
@@ -2965,8 +3141,6 @@
                 && mNetworkCapabilities.hasCapability(
                         NetworkCapabilities.NET_CAPABILITY_TRUSTED)
                 && mNetworkCapabilities.hasCapability(
-                        NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
-                && mNetworkCapabilities.hasCapability(
                         NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
     }
 
@@ -3008,6 +3182,12 @@
     /**
      * Request the data network to handover to the target transport.
      *
+     * This is the starting point of initiating IWLAN/cellular handover. It will first call
+     * {@link DataServiceManager#startHandover(int, Message)} to notify source transport that
+     * handover is about to start, and then call {@link DataServiceManager#setupDataCall(int,
+     * DataProfile, boolean, boolean, int, LinkProperties, int, NetworkSliceInfo, TrafficDescriptor,
+     * boolean, Message)} on target transport to initiate the handover process.
+     *
      * @param targetTransport The target transport.
      * @param retryEntry Data handover retry entry. This would be {@code null} for first time
      * handover attempt.
@@ -3020,12 +3200,16 @@
             if (retryEntry != null) retryEntry.setState(DataRetryEntry.RETRY_STATE_CANCELLED);
             return false;
         }
-        sendMessage(obtainMessage(EVENT_START_HANDOVER, targetTransport, 0, retryEntry));
+
+        // Before we really initiate the handover process on target transport, we need to notify
+        // source transport that handover is about to start. Handover will be eventually initiated
+        // in onStartHandover().
+        sendMessage(obtainMessage(EVENT_NOTIFY_HANDOVER_STARTED, 0, targetTransport, retryEntry));
         return true;
     }
 
     /**
-     * Called when handover between IWLAN and cellular is needed.
+     * Called when starting IWLAN/cellular handover process on the target transport.
      *
      * @param targetTransport The target transport.
      * @param retryEntry Data handover retry entry. This would be {@code null} for first time
@@ -3058,7 +3242,7 @@
                 && !mAttachedNetworkRequestList.isEmpty()) {
             TelephonyNetworkRequest networkRequest = mAttachedNetworkRequestList.get(0);
             DataProfile dataProfile = mDataNetworkController.getDataProfileManager()
-                    .getDataProfileForNetworkRequest(networkRequest, targetNetworkType);
+                    .getDataProfileForNetworkRequest(networkRequest, targetNetworkType, false);
             // Some carriers have different profiles between cellular and IWLAN. We need to
             // dynamically switch profile, but only when those profiles have same APN name.
             if (dataProfile != null && dataProfile.getApnSetting() != null
@@ -3080,7 +3264,6 @@
                 DataService.REQUEST_REASON_HANDOVER, mLinkProperties, mPduSessionId,
                 mNetworkSliceInfo, mHandoverDataProfile.getTrafficDescriptor(), true,
                 obtainMessage(EVENT_HANDOVER_RESPONSE, retryEntry));
-        transitionTo(mHandoverState);
     }
 
     /**
@@ -3096,7 +3279,7 @@
         logl("onHandoverResponse: resultCode=" + DataServiceCallback.resultCodeToString(resultCode)
                 + ", response=" + response);
         mFailCause = getFailCauseFromDataCallResponse(resultCode, response);
-        validateDataCallResponse(response);
+        validateDataCallResponse(response, false /*isSetupResponse*/);
         if (mFailCause == DataFailCause.NONE) {
             // Handover succeeded.
 
@@ -3123,15 +3306,23 @@
                     () -> mDataNetworkCallback.onHandoverSucceeded(DataNetwork.this));
         } else {
             // Handover failed.
+
+            // Notify source transport that handover failed on target transport so that PDU session
+            // id can be released if it is preserved for handover.
+            mDataServiceManagers.get(mTransport).cancelHandover(mCid.get(mTransport),
+                    obtainMessage(EVENT_NOTIFY_HANDOVER_CANCELLED_RESPONSE));
+
             long retry = response != null ? response.getRetryDurationMillis()
                     : DataCallResponse.RETRY_DURATION_UNDEFINED;
+            // If the handover mode is unspecified, default to HANDOVER_FAILURE_MODE_UNKNOWN,
+            // which will retry handover if retry rules are defined.
             int handoverFailureMode = response != null ? response.getHandoverFailureMode()
-                    : DataCallResponse.HANDOVER_FAILURE_MODE_LEGACY;
+                    : DataCallResponse.HANDOVER_FAILURE_MODE_UNKNOWN;
             if (retryEntry != null) retryEntry.setState(DataRetryEntry.RETRY_STATE_FAILED);
             mDataNetworkCallback.invokeFromExecutor(
                     () -> mDataNetworkCallback.onHandoverFailed(DataNetwork.this,
                             mFailCause, retry, handoverFailureMode));
-            mDataCallSessionStats.onHandoverFailure(mFailCause);
+            trackHandoverFailure(response != null ? response.getCause() : mFailCause);
         }
 
         // No matter handover succeeded or not, transit back to connected state.
@@ -3139,7 +3330,21 @@
     }
 
     /**
-     * Called when PCO data changes.
+     * Called when handover failed. Record the source and target RAT{@link NetworkType} and the
+     * failure cause {@link android.telephony.DataFailCause}.
+     *
+     * @param cause The fail cause.
+     */
+    private void trackHandoverFailure(int cause) {
+        int sourceRat = getDataNetworkType();
+        int targetTransport = DataUtils.getTargetTransport(mTransport);
+        int targetRat = getDataNetworkType(targetTransport);
+
+        mDataCallSessionStats.onHandoverFailure(cause, sourceRat, targetRat);
+    }
+
+    /**
+     * Called when receiving PCO (Protocol Configuration Options) data from the cellular network.
      *
      * @param pcoData The PCO data.
      */
@@ -3179,8 +3384,14 @@
     }
 
     /**
-     * @return The PCO data map of the network. The key is the PCO id, the value is the PCO data.
-     * An empty map if PCO data is not available (or when the network is on IWLAN).
+     * @return The last known data network type of the data network.
+     */
+    public @NetworkType int getLastKnownDataNetworkType() {
+        return mLastKnownDataNetworkType;
+    }
+
+    /**
+     * @return The PCO data received from the network.
      */
     public @NonNull Map<Integer, PcoData> getPcoData() {
         if (mTransport == AccessNetworkConstants.TRANSPORT_TYPE_WLAN
@@ -3205,6 +3416,17 @@
     }
 
     /**
+     * Check if any of the attached request has the specified network capability.
+     *
+     * @param netCapability The network capability to check.
+     * @return {@code true} if at least one network request has specified network capability.
+     */
+    public boolean hasNetworkCapabilityInNetworkRequests(@NetCapability int netCapability) {
+        return mAttachedNetworkRequestList.stream().anyMatch(
+                request -> request.hasCapability(netCapability));
+    }
+
+    /**
      * Convert the data tear down reason to string.
      *
      * @param reason Data deactivation reason.
@@ -3212,6 +3434,8 @@
      */
     public static @NonNull String tearDownReasonToString(@TearDownReason int reason) {
         switch (reason) {
+            case TEAR_DOWN_REASON_NONE:
+                return "NONE";
             case TEAR_DOWN_REASON_CONNECTIVITY_SERVICE_UNWANTED:
                 return "CONNECTIVITY_SERVICE_UNWANTED";
             case TEAR_DOWN_REASON_SIM_REMOVAL:
@@ -3289,6 +3513,8 @@
                 return "EVENT_ATTACH_NETWORK_REQUEST";
             case EVENT_DETACH_NETWORK_REQUEST:
                 return "EVENT_DETACH_NETWORK_REQUEST";
+            case EVENT_RADIO_NOT_AVAILABLE:
+                return "EVENT_RADIO_NOT_AVAILABLE";
             case EVENT_ALLOCATE_PDU_SESSION_ID_RESPONSE:
                 return "EVENT_ALLOCATE_PDU_SESSION_ID_RESPONSE";
             case EVENT_SETUP_DATA_NETWORK_RESPONSE:
@@ -3305,8 +3531,6 @@
                 return "EVENT_BANDWIDTH_ESTIMATE_FROM_MODEM_CHANGED";
             case EVENT_DISPLAY_INFO_CHANGED:
                 return "EVENT_DISPLAY_INFO_CHANGED";
-            case EVENT_START_HANDOVER:
-                return "EVENT_START_HANDOVER";
             case EVENT_HANDOVER_RESPONSE:
                 return "EVENT_HANDOVER_RESPONSE";
             case EVENT_SUBSCRIPTION_PLAN_OVERRIDE:
@@ -3327,6 +3551,12 @@
                 return "EVENT_VOICE_CALL_ENDED";
             case EVENT_CSS_INDICATOR_CHANGED:
                 return "EVENT_CSS_INDICATOR_CHANGED";
+            case EVENT_NOTIFY_HANDOVER_STARTED:
+                return "EVENT_NOTIFY_HANDOVER_STARTED";
+            case EVENT_NOTIFY_HANDOVER_STARTED_RESPONSE:
+                return "EVENT_NOTIFY_HANDOVER_STARTED_RESPONSE";
+            case EVENT_NOTIFY_HANDOVER_CANCELLED_RESPONSE:
+                return "EVENT_NOTIFY_HANDOVER_CANCELLED_RESPONSE";
             default:
                 return "Unknown(" + event + ")";
         }
@@ -3412,6 +3642,8 @@
         pw.increaseIndent();
         pw.println("mSubId=" + mSubId);
         pw.println("mTransport=" + AccessNetworkConstants.transportTypeToString(mTransport));
+        pw.println("mLastKnownDataNetworkType=" + TelephonyManager
+                .getNetworkTypeName(mLastKnownDataNetworkType));
         pw.println("WWAN cid=" + mCid.get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN));
         pw.println("WLAN cid=" + mCid.get(AccessNetworkConstants.TRANSPORT_TYPE_WLAN));
         pw.println("mNetworkScore=" + mNetworkScore);
diff --git a/src/java/com/android/internal/telephony/data/DataNetworkController.java b/src/java/com/android/internal/telephony/data/DataNetworkController.java
index 4b02428..127400b 100644
--- a/src/java/com/android/internal/telephony/data/DataNetworkController.java
+++ b/src/java/com/android/internal/telephony/data/DataNetworkController.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.telephony.data;
 
-
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -35,6 +34,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.telecom.TelecomManager;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
@@ -83,9 +83,9 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.SlidingWindowEventCounter;
-import com.android.internal.telephony.SubscriptionInfoUpdater;
 import com.android.internal.telephony.TelephonyComponentFactory;
 import com.android.internal.telephony.data.AccessNetworksManager.AccessNetworksManagerCallback;
+import com.android.internal.telephony.data.DataConfigManager.DataConfigManagerCallback;
 import com.android.internal.telephony.data.DataEvaluation.DataAllowedReason;
 import com.android.internal.telephony.data.DataEvaluation.DataDisallowedReason;
 import com.android.internal.telephony.data.DataEvaluation.DataEvaluationReason;
@@ -111,6 +111,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
@@ -134,15 +135,15 @@
 public class DataNetworkController extends Handler {
     private static final boolean VDBG = false;
 
-    /** Event for data config updated. */
-    private static final int EVENT_DATA_CONFIG_UPDATED = 1;
-
     /** Event for adding a network request. */
     private static final int EVENT_ADD_NETWORK_REQUEST = 2;
 
     /** Event for removing a network request. */
     private static final int EVENT_REMOVE_NETWORK_REQUEST = 3;
 
+    /** Event for SRVCC state changed. */
+    private static final int EVENT_SRVCC_STATE_CHANGED = 4;
+
     /** Re-evaluate all unsatisfied network requests. */
     private static final int EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS = 5;
 
@@ -222,6 +223,13 @@
     private static final long REEVALUATE_UNSATISFIED_NETWORK_REQUESTS_TAC_CHANGED_DELAY_MILLIS =
             TimeUnit.MILLISECONDS.toMillis(100);
 
+    /**
+     * The delay in milliseconds to re-evaluate unsatisfied network requests after network request
+     * detached.
+     */
+    private static final long REEVALUATE_UNSATISFIED_NETWORK_REQUESTS_AFTER_DETACHED_DELAY_MILLIS =
+            TimeUnit.SECONDS.toMillis(1);
+
     private final Phone mPhone;
     private final String mLogTag;
     private final LocalLog mLocalLog = new LocalLog(128);
@@ -233,6 +241,7 @@
     private final @NonNull AccessNetworksManager mAccessNetworksManager;
     private final @NonNull DataRetryManager mDataRetryManager;
     private final @NonNull ImsManager mImsManager;
+    private final @NonNull TelecomManager mTelecomManager;
     private final @NonNull NetworkPolicyManager mNetworkPolicyManager;
     private final @NonNull SparseArray<DataServiceManager> mDataServiceManagers =
             new SparseArray<>();
@@ -299,12 +308,15 @@
     private final @NonNull Set<DataNetworkControllerCallback> mDataNetworkControllerCallbacks =
             new ArraySet<>();
 
-    /** Indicates if packet switch data is restricted by the network. */
+    /** Indicates if packet switch data is restricted by the cellular network. */
     private boolean mPsRestricted = false;
 
     /** Indicates if NR advanced is allowed by PCO. */
     private boolean mNrAdvancedCapableByPco = false;
 
+    /** Indicates if srvcc is going on. */
+    private boolean mIsSrvccHandoverInProcess = false;
+
     /**
      * Indicates if the data services are bound. Key if the transport type, and value is the boolean
      * indicating service is bound or not.
@@ -463,6 +475,7 @@
             }
             return true;
         }
+
         /**
          * Get the first network request that contains all the provided network capabilities.
          *
@@ -548,10 +561,19 @@
         /**
          * 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.
+         *
+         * @param transport Transport for the connected network.
+         * @param dataProfile The data profile of the connected data network.
+         */
+        public void onDataNetworkConnected(@TransportType int transport,
+                @NonNull DataProfile dataProfile) {}
 
         /** Called when internet data network is disconnected. */
         public void onInternetDataNetworkDisconnected() {}
@@ -713,9 +735,9 @@
                         + "\"" + ruleString + "\"");
             }
 
-            if (source.contains(AccessNetworkType.UNKNOWN)) {
-                throw new IllegalArgumentException("Source access networks contains unknown. "
-                        + "\"" + ruleString + "\"");
+            if (source.contains(AccessNetworkType.UNKNOWN) && type != RULE_TYPE_DISALLOWED) {
+                throw new IllegalArgumentException("Unknown access network can be only specified in"
+                        + " the disallowed rule. \"" + ruleString + "\"");
             }
 
             if (target.contains(AccessNetworkType.UNKNOWN)) {
@@ -772,13 +794,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 ==========
@@ -884,28 +903,12 @@
                     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.shouldDelayImsTearDown()) {
-                            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);
         mNetworkPolicyManager = mPhone.getContext().getSystemService(NetworkPolicyManager.class);
+        mTelecomManager = mPhone.getContext().getSystemService(TelecomManager.class);
 
         // Use the raw one from ServiceStateTracker instead of the combined one from
         // mPhone.getServiceState().
@@ -961,8 +964,17 @@
         });
 
         mPhone.getServiceStateTracker().registerForServiceStateChanged(this,
-                EVENT_SERVICE_STATE_CHANGED);
-        mDataConfigManager.registerForConfigUpdate(this, EVENT_DATA_CONFIG_UPDATED);
+                EVENT_SERVICE_STATE_CHANGED, null);
+        mDataConfigManager.registerCallback(new DataConfigManagerCallback(this::post) {
+            @Override
+            public void onCarrierConfigChanged() {
+                DataNetworkController.this.onCarrierConfigUpdated();
+            }
+            @Override
+            public void onDeviceConfigChanged() {
+                DataNetworkController.this.onDeviceConfigUpdated();
+            }
+        });
         mPhone.getServiceStateTracker().registerForPsRestrictedEnabled(this,
                 EVENT_PS_RESTRICT_ENABLED, null);
         mPhone.getServiceStateTracker().registerForPsRestrictedDisabled(this,
@@ -972,12 +984,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);
-            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() {
@@ -999,6 +1009,7 @@
                     this, EVENT_VOICE_CALL_ENDED, null);
         }
         mPhone.mCi.registerForSlicingConfigChanged(this, EVENT_SLICE_CONFIG_CHANGED, null);
+        mPhone.mCi.registerForSrvccStateChanged(this, EVENT_SRVCC_STATE_CHANGED, null);
 
         mPhone.getLinkBandwidthEstimator().registerCallback(
                 new LinkBandwidthEstimatorCallback(this::post) {
@@ -1012,10 +1023,8 @@
 
     @Override
     public void handleMessage(@NonNull Message msg) {
+        AsyncResult ar;
         switch (msg.what) {
-            case EVENT_DATA_CONFIG_UPDATED:
-                onDataConfigUpdated();
-                break;
             case EVENT_REGISTER_ALL_EVENTS:
                 onRegisterAllEvents();
                 break;
@@ -1049,6 +1058,12 @@
                 sendMessage(obtainMessage(EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS,
                         DataEvaluationReason.SLICE_CONFIG_CHANGED));
                 break;
+            case EVENT_SRVCC_STATE_CHANGED:
+                ar = (AsyncResult) msg.obj;
+                if (ar.exception == null) {
+                    onSrvccStateChanged((int[]) ar.result);
+                }
+                break;
             case EVENT_PS_RESTRICT_ENABLED:
                 mPsRestricted = true;
                 break;
@@ -1065,7 +1080,7 @@
                         REEVALUATE_UNSATISFIED_NETWORK_REQUESTS_TAC_CHANGED_DELAY_MILLIS);
                 break;
             case EVENT_DATA_SERVICE_BINDING_CHANGED:
-                AsyncResult ar = (AsyncResult) msg.obj;
+                ar = (AsyncResult) msg.obj;
                 int transport = (int) ar.userObj;
                 boolean bound = (boolean) ar.result;
                 onDataServiceBindingChanged(transport, bound);
@@ -1078,7 +1093,13 @@
                 onTearDownAllDataNetworks(msg.arg1);
                 break;
             case EVENT_REGISTER_DATA_NETWORK_CONTROLLER_CALLBACK:
-                mDataNetworkControllerCallbacks.add((DataNetworkControllerCallback) msg.obj);
+                DataNetworkControllerCallback callback = (DataNetworkControllerCallback) msg.obj;
+                mDataNetworkControllerCallbacks.add(callback);
+                // Notify upon registering if no data networks currently exist.
+                if (mDataNetworkList.isEmpty()) {
+                    callback.invokeFromExecutor(
+                            () -> callback.onAnyDataNetworkExistingChanged(false));
+                }
                 break;
             case EVENT_UNREGISTER_DATA_NETWORK_CONTROLLER_CALLBACK:
                 mDataNetworkControllerCallbacks.remove((DataNetworkControllerCallback) msg.obj);
@@ -1106,8 +1127,8 @@
                 log("Subscription plans changed: " + Arrays.toString(plans));
                 mSubscriptionPlans.clear();
                 mSubscriptionPlans.addAll(Arrays.asList(plans));
-                mDataNetworkControllerCallbacks.forEach(callback -> callback.invokeFromExecutor(
-                        () -> callback.onSubscriptionPlanOverride()));
+                mDataNetworkControllerCallbacks.forEach(cb -> cb.invokeFromExecutor(
+                        () -> cb.onSubscriptionPlanOverride()));
                 break;
             case EVENT_SUBSCRIPTION_OVERRIDE:
                 int overrideMask = msg.arg1;
@@ -1126,8 +1147,8 @@
                             mUnmeteredOverrideNetworkTypes.remove(networkType);
                         }
                     }
-                    mDataNetworkControllerCallbacks.forEach(callback -> callback.invokeFromExecutor(
-                            () -> callback.onSubscriptionPlanOverride()));
+                    mDataNetworkControllerCallbacks.forEach(cb -> cb.invokeFromExecutor(
+                            () -> cb.onSubscriptionPlanOverride()));
                 } else if (overrideMask == NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_CONGESTED) {
                     log("Congested subscription override: override=" + override
                             + ", networkTypes=" + Arrays.stream(networkTypes)
@@ -1140,8 +1161,8 @@
                             mCongestedOverrideNetworkTypes.remove(networkType);
                         }
                     }
-                    mDataNetworkControllerCallbacks.forEach(callback -> callback.invokeFromExecutor(
-                            () -> callback.onSubscriptionPlanOverride()));
+                    mDataNetworkControllerCallbacks.forEach(cb -> cb.invokeFromExecutor(
+                            () -> cb.onSubscriptionPlanOverride()));
                 } else {
                     loge("Unknown override mask: " + overrideMask);
                 }
@@ -1300,7 +1321,7 @@
         if (nriRegState == NetworkRegistrationInfo.REGISTRATION_STATE_HOME
                 || nriRegState == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING) return true;
 
-        // If data is OOS on the non-DDS,
+        // If data is OOS as this device slot is not modem preferred(i.e. not active for internet),
         // attempt to attach PS on 2G/3G if CS connection is available.
         return ss.getVoiceRegState() == ServiceState.STATE_IN_SERVICE
                 && mPhone.getPhoneId() != PhoneSwitcher.getInstance().getPreferredDataPhoneId()
@@ -1329,6 +1350,17 @@
     }
 
     /**
+     * @param capabilities The Network Capabilities to be checked.
+     * @return {@code true} if the capabilities contain any capability that's exempt from the single
+     * PDN rule.
+     */
+    private boolean hasCapabilityExemptsFromSinglePdnRule(@NetCapability int[] capabilities) {
+        Set<Integer> exemptCapabilities =
+                mDataConfigManager.getCapabilitiesExemptFromSingleDataNetwork();
+        return Arrays.stream(capabilities).anyMatch(exemptCapabilities::contains);
+    }
+
+    /**
      * Evaluate if telephony frameworks would allow data setup for internet in current environment.
      *
      * @return {@code true} if the environment is allowed for internet data. {@code false} if not
@@ -1343,13 +1375,31 @@
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                         .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
                         .build(), mPhone);
+        // If one of the existing networks can satisfy the internet request, then internet is
+        // allowed.
+        if (mDataNetworkList.stream().anyMatch(dataNetwork -> internetRequest.canBeSatisfiedBy(
+                dataNetwork.getNetworkCapabilities()))) {
+            return true;
+        }
+
+        // If no existing network can satisfy the request, then check if we can possibly setup
+        // the internet network.
+
         DataEvaluation evaluation = evaluateNetworkRequest(internetRequest,
                 DataEvaluationReason.EXTERNAL_QUERY);
+        if (evaluation.containsOnly(DataDisallowedReason.ONLY_ALLOWED_SINGLE_NETWORK)) {
+            // If the only failed reason is only single network allowed, then check if the request
+            // can trump the current network.
+            return internetRequest.getPriority() > mDataNetworkList.stream()
+                    .map(DataNetwork::getPriority)
+                    .max(Comparator.comparing(Integer::valueOf))
+                    .orElse(0);
+        }
         return !evaluation.containsDisallowedReasons();
     }
 
     /**
-     * @return {@code true} internet is unmetered.
+     * @return {@code true} if internet is unmetered.
      */
     public boolean isInternetUnmetered() {
         return mDataNetworkList.stream()
@@ -1362,6 +1412,17 @@
     }
 
     /**
+     * @return {@code true} if all data networks are disconnected.
+     */
+    public boolean areAllDataDisconnected() {
+        if (!mDataNetworkList.isEmpty()) {
+            log("areAllDataDisconnected false due to: " + mDataNetworkList.stream()
+                    .map(DataNetwork::name).collect(Collectors.joining(", ")));
+        }
+        return mDataNetworkList.isEmpty();
+    }
+
+    /**
      * @return List of the reasons why internet data is not allowed. An empty list if internet
      * is allowed.
      */
@@ -1394,7 +1455,7 @@
         if (networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_EIMS)) {
             evaluation.addDataAllowedReason(DataAllowedReason.EMERGENCY_REQUEST);
             evaluation.setCandidateDataProfile(mDataProfileManager.getDataProfileForNetworkRequest(
-                    networkRequest, getDataNetworkType(transport)));
+                    networkRequest, getDataNetworkType(transport), true));
             networkRequest.setEvaluation(evaluation);
             log(evaluation.toString());
             return evaluation;
@@ -1446,8 +1507,8 @@
             evaluation.addDataDisallowedReason(DataDisallowedReason.ROAMING_DISABLED);
         }
 
-        // Check if data is restricted by the network.
-        if (mPsRestricted) {
+        // Check if data is restricted by the cellular network.
+        if (mPsRestricted && transport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
             evaluation.addDataDisallowedReason(DataDisallowedReason.DATA_RESTRICTED_BY_NETWORK);
         }
 
@@ -1481,13 +1542,12 @@
         }
 
         // Check if only one data network is allowed.
-        // Note any IMS network is ignored for the single-connection rule.
         if (isOnlySingleDataNetworkAllowed(transport)
-                && !networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) {
-            // if exists non-IMS network
+                && !hasCapabilityExemptsFromSinglePdnRule(networkRequest.getCapabilities())) {
+            // if exists not-exempt network.
             if (mDataNetworkList.stream()
-                    .anyMatch(dataNetwork -> !dataNetwork.getNetworkCapabilities()
-                                    .hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS))) {
+                    .anyMatch(dataNetwork -> !hasCapabilityExemptsFromSinglePdnRule(
+                            dataNetwork.getNetworkCapabilities().getCapabilities()))) {
                 evaluation.addDataDisallowedReason(
                         DataDisallowedReason.ONLY_ALLOWED_SINGLE_NETWORK);
             }
@@ -1508,20 +1568,23 @@
 
             if (!mDataSettingsManager.isDataEnabled()
                     && networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
-                    && mDataSettingsManager.isMmsAlwaysAllowed()) {
+                    && mDataSettingsManager.isMobileDataPolicyEnabled(TelephonyManager
+                    .MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED)) {
                 // We reach here when data is disabled, but MMS always-allowed is enabled.
                 // (Note that isDataEnabled(ApnSetting.TYPE_MMS) returns true in this case, so it
                 // would not generate any soft disallowed reason. We need to explicitly handle it.)
                 evaluation.addDataAllowedReason(DataAllowedReason.MMS_REQUEST);
             }
         } else if (!evaluation.containsHardDisallowedReasons()) {
-            if ((mPhone.isInEmergencyCall() || mPhone.isInEcm())
+            if ((mTelecomManager.isInEmergencyCall() || mPhone.isInEcm())
                     && networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
                 // Check if it's SUPL during emergency call.
                 evaluation.addDataAllowedReason(DataAllowedReason.EMERGENCY_SUPL);
             } else if (!networkRequest.hasCapability(
-                    NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)) {
-                // Check if request is restricted.
+                    NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) && !networkRequest
+                    .hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) {
+                // Check if request is restricted and not for tethering, which always comes with
+                // a restricted network request.
                 evaluation.addDataAllowedReason(DataAllowedReason.RESTRICTED_REQUEST);
             } else if (transport == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
                 // Check if request is unmetered (WiFi or unmetered APN).
@@ -1534,17 +1597,25 @@
         }
 
         // Check if there is any compatible data profile
+        int networkType = getDataNetworkType(transport);
+        if (networkType == TelephonyManager.NETWORK_TYPE_UNKNOWN
+                && transport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
+            // reach here when data is OOS but serviceStateAllowsPSAttach == true, so we adopt the
+            // voice RAT to select data profile
+            networkType = mServiceState.getVoiceNetworkType();
+        }
         DataProfile dataProfile = mDataProfileManager
-                .getDataProfileForNetworkRequest(networkRequest, getDataNetworkType(transport));
+                .getDataProfileForNetworkRequest(networkRequest, networkType,
+                        // If the evaluation is due to environmental changes, then we should ignore
+                        // the permanent failure reached earlier.
+                        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);
@@ -1648,16 +1719,18 @@
         }
 
         // Check if there are other network that has higher priority, and only single data network
-        // is allowed. Note IMS network is exempt from the single-connection rule.
+        // is allowed.
         if (isOnlySingleDataNetworkAllowed(dataNetwork.getTransport())
-                && !dataNetwork.getNetworkCapabilities()
-                .hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) {
+                && !hasCapabilityExemptsFromSinglePdnRule(
+                        dataNetwork.getNetworkCapabilities().getCapabilities())) {
             // If there is network request that has higher priority than this data network, then
             // tear down the network, regardless that network request is satisfied or not.
             if (mAllNetworkRequestList.stream()
                     .filter(request -> dataNetwork.getTransport()
                             == mAccessNetworksManager.getPreferredTransportByNetworkCapability(
                                     request.getApnTypeNetworkCapability()))
+                    .filter(request
+                            -> !hasCapabilityExemptsFromSinglePdnRule(request.getCapabilities()))
                     .anyMatch(request -> request.getPriority() > dataNetwork.getPriority())) {
                 evaluation.addDataDisallowedReason(
                         DataDisallowedReason.ONLY_ALLOWED_SINGLE_NETWORK);
@@ -1667,13 +1740,20 @@
             }
         }
 
-        // 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.
-            if (!dataNetwork.shouldDelayImsTearDown()) {
+        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,
@@ -1681,22 +1761,27 @@
                     if (nri != null) {
                         DataSpecificRegistrationInfo dsri = nri.getDataSpecificInfo();
                         if (dsri != null && dsri.getVopsSupportInfo() != null
-                                && !dsri.getVopsSupportInfo().isVopsSupported()) {
+                                && !dsri.getVopsSupportInfo().isVopsSupported()
+                                && !mDataConfigManager.shouldKeepNetworkUpInNonVops()) {
                             evaluation.addDataDisallowedReason(
                                     DataDisallowedReason.VOPS_NOT_SUPPORTED);
                         }
                     }
                 }
-            } 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()) {
@@ -1711,10 +1796,9 @@
         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.
@@ -1722,9 +1806,11 @@
                     && !dataProfile.getApnSetting().canSupportLingeringNetworkType(networkType)) {
                 log("networkType=" + TelephonyManager.getNetworkTypeName(networkType)
                         + ", networkTypeBitmask="
-                        + dataProfile.getApnSetting().getNetworkTypeBitmask()
+                        + TelephonyManager.convertNetworkTypeBitmaskToString(
+                                dataProfile.getApnSetting().getNetworkTypeBitmask())
                         + ", lingeringNetworkTypeBitmask="
-                        + dataProfile.getApnSetting().getLingeringNetworkTypeBitmask());
+                        + TelephonyManager.convertNetworkTypeBitmaskToString(
+                                dataProfile.getApnSetting().getLingeringNetworkTypeBitmask()));
                 evaluation.addDataDisallowedReason(
                         DataDisallowedReason.DATA_NETWORK_TYPE_NOT_ALLOWED);
             }
@@ -1734,20 +1820,18 @@
             evaluation.addDataDisallowedReason(DataDisallowedReason.DATA_DISABLED);
         }
 
-        // Check if the data profile is still valid, sometimes the users can remove it from the APN
-        // editor. We use very loose check here because APN id can change after APN reset to
-        // default
-        if (mDataProfileManager.getDataProfile(
-                dataProfile.getApnSetting() != null
-                        ? dataProfile.getApnSetting().getApnName() : null,
-                dataProfile.getTrafficDescriptor()) == null) {
+        // Check if the data profile is still compatible, sometimes the users can remove it from the
+        // APN editor. If some of the important fields are changed in APN settings, we need to
+        // tear down the network. Note traffic descriptor from the data profile will not be checked.
+        if (!mDataProfileManager.isDataProfileCompatible(dataProfile)) {
             evaluation.addDataDisallowedReason(DataDisallowedReason.DATA_PROFILE_INVALID);
         }
 
         // 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);
         }
 
@@ -1759,12 +1843,16 @@
             // If there are reasons we should tear down the network, check if those are hard reasons
             // or soft reasons. In some scenarios, we can make exceptions if they are soft
             // disallowed reasons.
-            if ((mPhone.isInEmergencyCall() || mPhone.isInEcm()) && dataNetwork.isEmergencySupl()) {
+            if ((mTelecomManager.isInEmergencyCall() || mPhone.isInEcm())
+                    && dataNetwork.isEmergencySupl()) {
                 // Check if it's SUPL during emergency call.
                 evaluation.addDataAllowedReason(DataAllowedReason.EMERGENCY_SUPL);
             } else if (!dataNetwork.getNetworkCapabilities().hasCapability(
-                    NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)) {
-                // Check if request is restricted
+                    NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                    && !dataNetwork.hasNetworkCapabilityInNetworkRequests(
+                            NetworkCapabilities.NET_CAPABILITY_DUN)) {
+                // Check if request is restricted and there are no DUN network requests attached to
+                // the network.
                 evaluation.addDataAllowedReason(DataAllowedReason.RESTRICTED_REQUEST);
             } else if (dataNetwork.getTransport() == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
                 // Check if request is unmetered (WiFi or unmetered APN)
@@ -1779,7 +1867,16 @@
             }
         }
 
-        log("Evaluated " + dataNetwork + ", " + evaluation.toString());
+        // 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;
     }
 
@@ -1821,11 +1918,6 @@
             return dataEvaluation;
         }
 
-        if (mDataRetryManager.isAnyHandoverRetryScheduled(dataNetwork)) {
-            dataEvaluation.addDataDisallowedReason(DataDisallowedReason.RETRY_SCHEDULED);
-            return dataEvaluation;
-        }
-
         // If enhanced handover check is enabled, perform extra checks.
         if (mDataConfigManager.isEnhancedIwlanHandoverCheckEnabled()) {
             int targetTransport = DataUtils.getTargetTransport(dataNetwork.getTransport());
@@ -1845,7 +1937,8 @@
                     DataSpecificRegistrationInfo dsri = nri.getDataSpecificInfo();
                     // Check if the network is non-VoPS.
                     if (dsri != null && dsri.getVopsSupportInfo() != null
-                            && !dsri.getVopsSupportInfo().isVopsSupported()) {
+                            && !dsri.getVopsSupportInfo().isVopsSupported()
+                            && !mDataConfigManager.shouldKeepNetworkUpInNonVops()) {
                         dataEvaluation.addDataDisallowedReason(
                                 DataDisallowedReason.VOPS_NOT_SUPPORTED);
                     }
@@ -1860,8 +1953,18 @@
         if (mDataConfigManager.isIwlanHandoverPolicyEnabled()) {
             List<HandoverRule> handoverRules = mDataConfigManager.getHandoverRules();
 
+            int sourceNetworkType = getDataNetworkType(dataNetwork.getTransport());
+            if (sourceNetworkType == TelephonyManager.NETWORK_TYPE_UNKNOWN) {
+                // Using the data network type stored in the data network. We
+                // cache the last known network type in data network controller
+                // because data network has much shorter life cycle. It can prevent
+                // the obsolete last known network type cached in data network
+                // type controller.
+                sourceNetworkType = dataNetwork.getLastKnownDataNetworkType();
+            }
             int sourceAccessNetwork = DataUtils.networkTypeToAccessNetworkType(
-                    getDataNetworkType(dataNetwork.getTransport()));
+                    sourceNetworkType);
+
             int targetAccessNetwork = DataUtils.networkTypeToAccessNetworkType(
                     getDataNetworkType(DataUtils.getTargetTransport(dataNetwork.getTransport())));
             NetworkCapabilities capabilities = dataNetwork.getNetworkCapabilities();
@@ -1965,9 +2068,11 @@
                     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 0;
+        return DataNetwork.TEAR_DOWN_REASON_NONE;
     }
 
     /**
@@ -2050,7 +2155,8 @@
         }
 
         if (networkRequest.getAttachedNetwork() != null) {
-            networkRequest.getAttachedNetwork().detachNetworkRequest(networkRequest);
+            networkRequest.getAttachedNetwork().detachNetworkRequest(
+                        networkRequest, false /* shouldRetry */);
         }
         log("onRemoveNetworkRequest: Removed " + networkRequest);
     }
@@ -2067,16 +2173,19 @@
     }
 
     /**
-     * Check if there are existing networks having the same interface name.
+     * Get data network by interface name.
      *
-     * @param interfaceName The interface name to check.
-     * @return {@code true} if the existing network has the same interface name.
+     * @param interfaceName The network interface name.
+     * @return The data network if found.
      */
-    public boolean isNetworkInterfaceExisting(@NonNull String interfaceName) {
+    @Nullable
+    public DataNetwork getDataNetworkByInterface(@NonNull String interfaceName) {
         return mDataNetworkList.stream()
                 .filter(dataNetwork -> !dataNetwork.isDisconnecting())
-                .anyMatch(dataNetwork -> interfaceName.equals(
-                        dataNetwork.getLinkProperties().getInterfaceName()));
+                .filter(dataNetwork -> interfaceName.equals(
+                        dataNetwork.getLinkProperties().getInterfaceName()))
+                .findFirst()
+                .orElse(null);
     }
 
     /**
@@ -2239,19 +2348,23 @@
     }
 
     /**
-     * Called when data config was updated.
+     * Called when carrier config was updated.
      */
-    private void onDataConfigUpdated() {
-        log("onDataConfigUpdated: config is "
+    private void onCarrierConfigUpdated() {
+        log("onCarrierConfigUpdated: config is "
                 + (mDataConfigManager.isConfigCarrierSpecific() ? "" : "not ")
                 + "carrier specific. mSimState="
-                + SubscriptionInfoUpdater.simStateString(mSimState)
-                + ". DeviceConfig updated.");
-
-        updateAnomalySlidingWindowCounters();
+                + TelephonyManager.simStateToString(mSimState));
         updateNetworkRequestsPriority();
-        sendMessage(obtainMessage(EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS,
-                DataEvaluationReason.DATA_CONFIG_CHANGED));
+        onReevaluateUnsatisfiedNetworkRequests(DataEvaluationReason.DATA_CONFIG_CHANGED);
+    }
+
+    /**
+     * Called when device config was updated.
+     */
+    private void onDeviceConfigUpdated() {
+        log("onDeviceConfigUpdated: DeviceConfig updated.");
+        updateAnomalySlidingWindowCounters();
     }
 
     /**
@@ -2326,9 +2439,12 @@
                 + dataSetupRetryEntry + ", allowed reason=" + allowedReason + ", service state="
                 + mServiceState);
         for (DataNetwork dataNetwork : mDataNetworkList) {
-            if (dataNetwork.getDataProfile().equals(dataProfile)) {
+            DataProfile currentDataProfile = dataNetwork.getDataProfile();
+            if (dataProfile.equals(currentDataProfile)
+                    || mDataProfileManager.areDataProfilesSharingApn(
+                            dataProfile, currentDataProfile)) {
                 log("onSetupDataNetwork: Found existing data network " + dataNetwork
-                        + " has the same data profile.");
+                        + " using the same or a similar data profile.");
                 if (dataSetupRetryEntry != null) {
                     dataSetupRetryEntry.setState(DataRetryEntry.RETRY_STATE_CANCELLED);
                 }
@@ -2399,9 +2515,9 @@
 
                     @Override
                     public void onDisconnected(@NonNull DataNetwork dataNetwork,
-                            @DataFailureCause int cause) {
+                            @DataFailureCause int cause, @TearDownReason int tearDownReason) {
                         DataNetworkController.this.onDataNetworkDisconnected(
-                                dataNetwork, cause);
+                                dataNetwork, cause, tearDownReason);
                     }
 
                     @Override
@@ -2437,6 +2553,13 @@
                     public void onTrackNetworkUnwanted(@NonNull DataNetwork dataNetwork) {
                         DataNetworkController.this.onTrackNetworkUnwanted();
                     }
+
+                    @Override
+                    public void onRetryUnsatisfiedNetworkRequest(
+                            @NonNull TelephonyNetworkRequest networkRequest) {
+                        DataNetworkController.this.onRetryUnsatisfiedNetworkRequest(
+                                networkRequest);
+                    }
                 }
         ));
         if (!mAnyDataNetworkExisting) {
@@ -2460,7 +2583,7 @@
         logl("onDataNetworkSetupDataFailed: " + dataNetwork + ", cause="
                 + DataFailCause.toString(cause) + ", retryDelayMillis=" + retryDelayMillis + "ms.");
         mDataNetworkList.remove(dataNetwork);
-        trackSetupDataCallFailure(dataNetwork.getTransport());
+        trackSetupDataCallFailure(dataNetwork.getTransport(), cause);
         if (mAnyDataNetworkExisting && mDataNetworkList.isEmpty()) {
             mPendingTearDownAllNetworks = false;
             mAnyDataNetworkExisting = false;
@@ -2485,8 +2608,10 @@
      * {@link AccessNetworkConstants.TransportType} data service.
      *
      * @param transport The transport of the data service.
+     * @param cause The fail cause
      */
-    private void trackSetupDataCallFailure(@TransportType int transport) {
+    private void trackSetupDataCallFailure(@TransportType int transport,
+            @DataFailureCause int cause) {
         switch (transport) {
             case AccessNetworkConstants.TRANSPORT_TYPE_WWAN:
                 // Skip when poor signal strength
@@ -2494,6 +2619,10 @@
                         <= CellSignalStrength.SIGNAL_STRENGTH_POOR) {
                     return;
                 }
+                if (cause == DataFailCause.ERROR_UNSPECIFIED || cause == DataFailCause.UNKNOWN) {
+                    reportAnomaly("RIL set up data call fails: unknown/unspecified error",
+                            "ce7d1465-d8e4-404a-b76f-de2c60bee843");
+                }
                 if (mSetupDataCallWwanFailureCounter.addOccurrence()) {
                     reportAnomaly("RIL fails setup data call request "
                                     + mSetupDataCallWwanFailureCounter.getFrequencyString(),
@@ -2501,6 +2630,10 @@
                 }
                 break;
             case AccessNetworkConstants.TRANSPORT_TYPE_WLAN:
+                if (cause == DataFailCause.ERROR_UNSPECIFIED || cause == DataFailCause.UNKNOWN) {
+                    reportAnomaly("IWLAN set up data call fails: unknown/unspecified error",
+                            "a16fc15c-815b-4908-b8e6-5f3bc7cbc20b");
+                }
                 if (mSetupDataCallWlanFailureCounter.addOccurrence()) {
                     reportAnomaly("IWLAN data service fails setup data call request "
                                     + mSetupDataCallWlanFailureCounter.getFrequencyString(),
@@ -2530,6 +2663,11 @@
      */
     private void onDataNetworkConnected(@NonNull DataNetwork dataNetwork) {
         logl("onDataNetworkConnected: " + dataNetwork);
+
+        mDataNetworkControllerCallbacks.forEach(callback -> callback.invokeFromExecutor(
+                () -> callback.onDataNetworkConnected(dataNetwork.getTransport(),
+                        dataNetwork.getDataProfile())));
+
         mPreviousConnectedDataNetworkList.add(0, dataNetwork);
         // Preserve the connected data networks for debugging purposes.
         if (mPreviousConnectedDataNetworkList.size() > MAX_HISTORICAL_CONNECTED_DATA_NETWORKS) {
@@ -2558,11 +2696,22 @@
         NetworkRequestList requestList = new NetworkRequestList(
                 dataSetupRetryEntry.networkRequestList);
         requestList.removeIf(request -> !mAllNetworkRequestList.contains(request));
+        // Retrieves the newly added unsatisfied NetworkRequest if all NetworkRequests in the
+        // DataSetupRetryEntry have already been removed.
+        if (requestList.isEmpty()) {
+            List<NetworkRequestList> groupRequestLists = getGroupedUnsatisfiedNetworkRequests();
+            dataSetupRetryEntry.networkRequestList.stream()
+                    .filter(request -> groupRequestLists.stream()
+                            .anyMatch(groupRequestList -> groupRequestList
+                                    .get(request.getCapabilities()) != null))
+                    .forEach(requestList::add);
+        }
         if (requestList.isEmpty()) {
             loge("onDataNetworkSetupRetry: Request list is empty. Abort retry.");
             dataSetupRetryEntry.setState(DataRetryEntry.RETRY_STATE_CANCELLED);
             return;
         }
+        log("onDataNetworkSetupRetry: Request list:" + requestList);
         TelephonyNetworkRequest telephonyNetworkRequest = requestList.get(0);
 
         int networkCapability = telephonyNetworkRequest.getApnTypeNetworkCapability();
@@ -2632,10 +2781,36 @@
             return;
         }
 
-        logl("Start handover " + dataNetwork + " to "
+        logl("onDataNetworkHandoverRetry: Start handover " + dataNetwork + " to "
                 + AccessNetworkConstants.transportTypeToString(preferredTransport)
                 + ", " + dataHandoverRetryEntry);
-        dataNetwork.startHandover(preferredTransport, dataHandoverRetryEntry);
+        tryHandoverDataNetwork(dataNetwork, preferredTransport, dataHandoverRetryEntry);
+    }
+
+    /**
+     * 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);
     }
 
     /**
@@ -2708,11 +2883,13 @@
      *
      * @param dataNetwork The data network.
      * @param cause The disconnect cause.
+     * @param tearDownReason The reason the network was torn down
      */
     private void onDataNetworkDisconnected(@NonNull DataNetwork dataNetwork,
-            @DataFailureCause int cause) {
+            @DataFailureCause int cause, @TearDownReason int tearDownReason) {
         logl("onDataNetworkDisconnected: " + dataNetwork + ", cause="
-                + DataFailCause.toString(cause) + "(" + cause + ")");
+                + DataFailCause.toString(cause) + "(" + cause + "), tearDownReason="
+                + DataNetwork.tearDownReasonToString(tearDownReason));
         mDataNetworkList.remove(dataNetwork);
         mPendingImsDeregDataNetworks.remove(dataNetwork);
         mDataRetryManager.cancelPendingHandoverRetry(dataNetwork);
@@ -2733,11 +2910,13 @@
                     () -> callback.onAnyDataNetworkExistingChanged(mAnyDataNetworkExisting)));
         }
 
+        // Immediately reestablish on target transport if network was torn down due to policy
+        long delayMillis = tearDownReason == DataNetwork.TEAR_DOWN_REASON_HANDOVER_NOT_ALLOWED
+                ? 0 : mDataConfigManager.getRetrySetupAfterDisconnectMillis();
         // Sometimes network was unsolicitedly reported lost for reasons. We should re-evaluate
         // and see if data network can be re-established again.
         sendMessageDelayed(obtainMessage(EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS,
-                DataEvaluationReason.RETRY_AFTER_DISCONNECTED),
-                mDataConfigManager.getRetrySetupAfterDisconnectMillis());
+                        DataEvaluationReason.RETRY_AFTER_DISCONNECTED), delayMillis);
     }
 
     /**
@@ -2802,6 +2981,16 @@
         } else if (handoverFailureMode == DataCallResponse
                 .HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL || handoverFailureMode
                 == DataCallResponse.HANDOVER_FAILURE_MODE_LEGACY) {
+            int preferredTransport = mAccessNetworksManager
+                    .getPreferredTransportByNetworkCapability(
+                            dataNetwork.getApnTypeNetworkCapability());
+            if (dataNetwork.getTransport() == preferredTransport) {
+                log("onDataNetworkHandoverFailed: Already on preferred transport "
+                        + AccessNetworkConstants.transportTypeToString(preferredTransport)
+                        + ". No further actions needed.");
+                return;
+            }
+
             int targetTransport = DataUtils.getTargetTransport(dataNetwork.getTransport());
             mDataRetryManager.evaluateDataSetupRetry(dataNetwork.getDataProfile(), targetTransport,
                     dataNetwork.getAttachedNetworkRequestList(), cause, retryDelayMillis);
@@ -2825,6 +3014,20 @@
     }
 
     /**
+     * Called when a network request is detached from the data network and should be retried.
+     *
+     * @param networkRequest The detached network request.
+     */
+    private void onRetryUnsatisfiedNetworkRequest(
+            @NonNull TelephonyNetworkRequest networkRequest) {
+        if (!mAllNetworkRequestList.contains(networkRequest)) return;
+
+        sendMessageDelayed(obtainMessage(EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS,
+                        DataEvaluationReason.UNSATISFIED_REQUEST_DETACHED),
+                REEVALUATE_UNSATISFIED_NETWORK_REQUESTS_AFTER_DETACHED_DELAY_MILLIS);
+    }
+
+    /**
      * Called when data stall occurs and needed to tear down / setup a new data network for
      * internet. This event is from {@link DataStallRecoveryManager}.
      */
@@ -2840,6 +3043,25 @@
     }
 
     /**
+     * Called when SRVCC handover state changes. To preserve the voice call, we don't tear down the
+     * IMS network while handover in process. We reevaluate the network when handover ends.
+     *
+     * @param state The handover state of SRVCC
+     */
+    private void onSrvccStateChanged(@NonNull int[] state) {
+        if (state != null && state.length != 0) {
+            log("onSrvccStateChanged: " + TelephonyManager.srvccStateToString(state[0]));
+            mIsSrvccHandoverInProcess = state[0] == TelephonyManager.SRVCC_STATE_HANDOVER_STARTED;
+            // Reevaluate networks if SRVCC ends.
+            if (!mIsSrvccHandoverInProcess
+                    && !hasMessages(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS)) {
+                sendMessage(obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS,
+                        DataEvaluationReason.SRVCC_STATE_CHANGED));
+            }
+        }
+    }
+
+    /**
      * Called when data service binding changed.
      *
      * @param transport The transport of the changed data service.
@@ -2871,7 +3093,7 @@
      * @param simState SIM state. (Note this is mixed with card state and application state.)
      */
     private void onSimStateChanged(@SimState int simState) {
-        log("onSimStateChanged: state=" + SubscriptionInfoUpdater.simStateString(simState));
+        log("onSimStateChanged: state=" + TelephonyManager.simStateToString(simState));
         if (mSimState != simState) {
             mSimState = simState;
             if (simState == TelephonyManager.SIM_STATE_ABSENT) {
@@ -2912,34 +3134,64 @@
                     continue;
                 }
 
-                DataEvaluation dataEvaluation = evaluateDataNetworkHandover(dataNetwork);
-                log("onEvaluatePreferredTransport: " + dataEvaluation + ", " + dataNetwork);
-                if (!dataEvaluation.containsDisallowedReasons()) {
-                    logl("Start handover " + dataNetwork + " to "
-                            + AccessNetworkConstants.transportTypeToString(preferredTransport));
-                    dataNetwork.startHandover(preferredTransport, null);
-                } else if (dataEvaluation.containsAny(DataDisallowedReason.NOT_ALLOWED_BY_POLICY,
-                        DataDisallowedReason.NOT_IN_SERVICE,
-                        DataDisallowedReason.VOPS_NOT_SUPPORTED)) {
-                    logl("onEvaluatePreferredTransport: Handover not allowed. Tear "
-                            + "down " + dataNetwork + " so a new network can be setup on "
-                            + AccessNetworkConstants.transportTypeToString(preferredTransport)
-                            + ".");
-                    tearDownGracefully(dataNetwork,
-                            DataNetwork.TEAR_DOWN_REASON_HANDOVER_NOT_ALLOWED);
-                } else if (dataEvaluation.containsAny(DataDisallowedReason.ILLEGAL_STATE,
-                        DataDisallowedReason.RETRY_SCHEDULED)) {
-                    logl("onEvaluatePreferredTransport: Handover not allowed. " + dataNetwork
-                            + " will remain on " + AccessNetworkConstants.transportTypeToString(
-                                    dataNetwork.getTransport()));
-                } else {
-                    loge("onEvaluatePreferredTransport: Unexpected handover evaluation result.");
-                }
+                tryHandoverDataNetwork(dataNetwork, preferredTransport, null/*handoverRetryEntry*/);
             }
         }
     }
 
     /**
+     * Perform data network handover if condition allows, otherwise tear down the network to allow
+     * new network setup on the target transport.
+     *
+     * @param dataNetwork The network on which the handover occurs
+     * @param targetTransport The target transport of the handover
+     * @param dataHandoverRetryEntry {@code null} if the handover attempt is not due to scheduled
+     *                                           retry
+     */
+    private void tryHandoverDataNetwork(@NonNull DataNetwork dataNetwork,
+            @TransportType int targetTransport,
+            @Nullable DataHandoverRetryEntry dataHandoverRetryEntry) {
+        if (dataHandoverRetryEntry == null // This handover is a new request
+                && mDataRetryManager.isAnyHandoverRetryScheduled(dataNetwork)) {
+            log("tryHandoverDataNetwork: retry scheduled for" + dataNetwork
+                    + ", ignore this attempt");
+            return;
+        }
+        DataEvaluation dataEvaluation = evaluateDataNetworkHandover(dataNetwork);
+        log("tryHandoverDataNetwork: " + dataEvaluation + ", " + dataNetwork);
+        if (!dataEvaluation.containsDisallowedReasons()) {
+            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)) {
+            logl("tryHandoverDataNetwork: Handover not allowed. Tear down"
+                    + dataNetwork + " so a new network can be setup on "
+                    + AccessNetworkConstants.transportTypeToString(targetTransport));
+            tearDownGracefully(dataNetwork,
+                    DataNetwork.TEAR_DOWN_REASON_HANDOVER_NOT_ALLOWED);
+        } else if (dataEvaluation.containsAny(DataDisallowedReason.ILLEGAL_STATE,
+                DataDisallowedReason.RETRY_SCHEDULED)) {
+            logl("tryHandoverDataNetwork: Handover not allowed. " + dataNetwork
+                    + " will remain on " + AccessNetworkConstants.transportTypeToString(
+                    dataNetwork.getTransport()));
+        } else {
+            loge("tryHandoverDataNetwork: Unexpected handover evaluation result.");
+        }
+    }
+
+    /**
      * Update {@link SubscriptionPlan}s from {@link NetworkPolicyManager}.
      */
     private void updateSubscriptionPlans() {
@@ -3034,6 +3286,12 @@
             log("Found more network requests that can be satisfied. " + networkRequestList);
             dataNetwork.attachNetworkRequests(networkRequestList);
         }
+
+        if (dataNetwork.getNetworkCapabilities().hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+            // Update because DataNetwork#isInternetSupported might have changed with capabilities.
+            updateOverallInternetDataState();
+        }
     }
 
     /**
@@ -3184,9 +3442,9 @@
     }
 
     /**
-     * Update the internet data network state. For now only {@link TelephonyManager#DATA_CONNECTED}
-     * , {@link TelephonyManager#DATA_SUSPENDED}, and
-     * {@link TelephonyManager#DATA_DISCONNECTED} are supported.
+     * Update the internet data network state. For now only {@link TelephonyManager#DATA_CONNECTED},
+     * {@link TelephonyManager#DATA_SUSPENDED}, and {@link TelephonyManager#DATA_DISCONNECTED}
+     * are supported.
      */
     private void updateOverallInternetDataState() {
         boolean anyInternetConnected = mDataNetworkList.stream()
@@ -3219,9 +3477,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(
@@ -3454,8 +3710,8 @@
 
     /**
      * Get the internet data network state. Note that this is the best effort if more than one
-     * data network supports internet. For now only {@link TelephonyManager#DATA_CONNECTED}
-     * , {@link TelephonyManager#DATA_SUSPENDED}, and {@link TelephonyManager#DATA_DISCONNECTED}
+     * data network supports internet. For now only {@link TelephonyManager#DATA_CONNECTED},
+     * {@link TelephonyManager#DATA_SUSPENDED}, and {@link TelephonyManager#DATA_DISCONNECTED}
      * are supported.
      *
      * @return The data network state.
@@ -3565,7 +3821,8 @@
         pw.println("mImsDataNetworkState="
                 + TelephonyUtils.dataStateToString(mImsDataNetworkState));
         pw.println("mDataServiceBound=" + mDataServiceBound);
-        pw.println("mSimState=" + SubscriptionInfoUpdater.simStateString(mSimState));
+        pw.println("mIsSrvccHandoverInProcess=" + mIsSrvccHandoverInProcess);
+        pw.println("mSimState=" + TelephonyManager.simStateToString(mSimState));
         pw.println("mDataNetworkControllerCallbacks=" + mDataNetworkControllerCallbacks);
         pw.println("Subscription plans:");
         pw.increaseIndent();
diff --git a/src/java/com/android/internal/telephony/data/DataProfileManager.java b/src/java/com/android/internal/telephony/data/DataProfileManager.java
index 1ec345c..37f0de0 100644
--- a/src/java/com/android/internal/telephony/data/DataProfileManager.java
+++ b/src/java/com/android/internal/telephony/data/DataProfileManager.java
@@ -32,6 +32,7 @@
 import android.provider.Telephony;
 import android.telephony.Annotation;
 import android.telephony.Annotation.NetworkType;
+import android.telephony.AnomalyReporter;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -44,6 +45,7 @@
 import android.util.LocalLog;
 
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.data.DataConfigManager.DataConfigManagerCallback;
 import com.android.internal.telephony.data.DataNetworkController.DataNetworkControllerCallback;
 import com.android.telephony.Rlog;
 
@@ -54,6 +56,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.UUID;
 import java.util.concurrent.Executor;
 import java.util.stream.Collectors;
 
@@ -64,9 +67,6 @@
 public class DataProfileManager extends Handler {
     private static final boolean VDBG = true;
 
-    /** Event for data config updated. */
-    private static final int EVENT_DATA_CONFIG_UPDATED = 1;
-
     /** Event for APN database changed. */
     private static final int EVENT_APN_DATABASE_CHANGED = 2;
 
@@ -77,6 +77,13 @@
     private final String mLogTag;
     private final LocalLog mLocalLog = new LocalLog(128);
 
+    /**
+     * Should only be used by update updateDataProfiles() to indicate whether resend IA to modem
+     * regardless whether IA changed.
+     **/
+    private final boolean FORCED_UPDATE_IA = true;
+    private final boolean ONLY_UPDATE_IA_IF_CHANGED = false;
+
     /** Data network controller. */
     private final @NonNull DataNetworkController mDataNetworkController;
 
@@ -86,7 +93,11 @@
     /** Cellular data service. */
     private final @NonNull DataServiceManager mWwanDataServiceManager;
 
-    /** All data profiles for the current carrier. */
+    /**
+     * All data profiles for the current carrier. Note only data profiles loaded from the APN
+     * database will be stored here. The on-demand data profiles (generated dynamically, for
+     * example, enterprise data profiles with differentiator) are not stored here.
+     */
     private final @NonNull List<DataProfile> mAllDataProfiles = new ArrayList<>();
 
     /** The data profile used for initial attach. */
@@ -95,6 +106,9 @@
     /** The preferred data profile used for internet. */
     private @Nullable DataProfile mPreferredDataProfile = null;
 
+    /** The last data profile that's successful for internet connection. */
+    private @Nullable DataProfile mLastInternetDataProfile = null;
+
     /** Preferred data profile set id. */
     private int mPreferredDataProfileSetId = Telephony.Carriers.NO_APN_SET_ID;
 
@@ -153,10 +167,16 @@
                 new DataNetworkControllerCallback(this::post) {
                     @Override
                     public void onInternetDataNetworkConnected(
-                            @NonNull List<DataProfile> dataProfiles) {
-                        DataProfileManager.this.onInternetDataNetworkConnected(dataProfiles);
-                    }});
-        mDataConfigManager.registerForConfigUpdate(this, EVENT_DATA_CONFIG_UPDATED);
+                            @NonNull List<DataNetwork> internetNetworks) {
+                        DataProfileManager.this.onInternetDataNetworkConnected(internetNetworks);
+                    }
+                });
+        mDataConfigManager.registerCallback(new DataConfigManagerCallback(this::post) {
+            @Override
+            public void onCarrierConfigChanged() {
+                DataProfileManager.this.onCarrierConfigUpdated();
+            }
+        });
         mPhone.getContext().getContentResolver().registerContentObserver(
                 Telephony.Carriers.CONTENT_URI, true, new ContentObserver(this) {
                     @Override
@@ -171,16 +191,13 @@
     @Override
     public void handleMessage(Message msg) {
         switch (msg.what) {
-            case EVENT_DATA_CONFIG_UPDATED:
-                onDataConfigUpdated();
-                break;
             case EVENT_SIM_REFRESH:
                 log("Update data profiles due to SIM refresh.");
-                updateDataProfiles();
+                updateDataProfiles(FORCED_UPDATE_IA);
                 break;
             case EVENT_APN_DATABASE_CHANGED:
                 log("Update data profiles due to APN db updated.");
-                updateDataProfiles();
+                updateDataProfiles(ONLY_UPDATE_IA_IF_CHANGED);
                 break;
             default:
                 loge("Unexpected event " + msg);
@@ -189,11 +206,11 @@
     }
 
     /**
-     * Called when data config was updated.
+     * Called when carrier config was updated.
      */
-    private void onDataConfigUpdated() {
-        log("Update data profiles due to config updated.");
-        updateDataProfiles();
+    private void onCarrierConfigUpdated() {
+        log("Update data profiles due to carrier config updated.");
+        updateDataProfiles(FORCED_UPDATE_IA);
 
         //TODO: more works needed to be done here.
     }
@@ -231,8 +248,10 @@
     /**
      * Update all data profiles, including preferred data profile, and initial attach data profile.
      * Also send those profiles down to the modem if needed.
+     *
+     * @param forceUpdateIa If {@code true}, we should always send IA again to modem.
      */
-    private void updateDataProfiles() {
+    private void updateDataProfiles(boolean forceUpdateIa) {
         List<DataProfile> profiles = new ArrayList<>();
         if (mDataConfigManager.isConfigCarrierSpecific()) {
             Cursor cursor = mPhone.getContext().getContentResolver().query(
@@ -242,7 +261,7 @@
                 loge("Cannot access APN database through telephony provider.");
                 return;
             }
-
+            boolean isInternetSupported = false;
             while (cursor.moveToNext()) {
                 ApnSetting apn = ApnSetting.makeApnSetting(cursor);
                 if (apn != null) {
@@ -253,14 +272,44 @@
                             .build();
                     profiles.add(dataProfile);
                     log("Added " + dataProfile);
+
+                    isInternetSupported |= apn.canHandleType(ApnSetting.TYPE_DEFAULT);
+                    if (mDataConfigManager.isApnConfigAnomalyReportEnabled()) {
+                        checkApnSetting(apn);
+                    }
                 }
             }
             cursor.close();
+
+            if (!isInternetSupported
+                    && !profiles.isEmpty() // APN database has been read successfully
+                    && mDataConfigManager.isApnConfigAnomalyReportEnabled()) {
+                reportAnomaly("Carrier doesn't support internet.",
+                        "9af73e18-b523-4dc5-adab-363eb6613305");
+            }
+        }
+
+        DataProfile dataProfile;
+
+        if (!profiles.isEmpty()) { // APN database has been read successfully after SIM loaded
+            // Check if any of the profile already supports IMS, if not, add the default one.
+            dataProfile = profiles.stream()
+                    .filter(dp -> dp.canSatisfy(NetworkCapabilities.NET_CAPABILITY_IMS))
+                    .findFirst()
+                    .orElse(null);
+            if (dataProfile == null) {
+                profiles.add(new DataProfile.Builder()
+                        .setApnSetting(buildDefaultApnSetting("DEFAULT IMS", "ims",
+                                ApnSetting.TYPE_IMS))
+                        .setTrafficDescriptor(new TrafficDescriptor("ims", null))
+                        .build());
+                log("Added default IMS data profile.");
+            }
         }
 
         // Check if any of the profile already supports ENTERPRISE, if not, check if DPC has
         // configured one and retrieve the same.
-        DataProfile dataProfile = profiles.stream()
+        dataProfile = profiles.stream()
                 .filter(dp -> dp.canSatisfy(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE))
                 .findFirst()
                 .orElse(null);
@@ -272,20 +321,6 @@
             }
         }
 
-        // Check if any of the profile already supports IMS, if not, add the default one.
-        dataProfile = profiles.stream()
-                .filter(dp -> dp.canSatisfy(NetworkCapabilities.NET_CAPABILITY_IMS))
-                .findFirst()
-                .orElse(null);
-        if (dataProfile == null) {
-            profiles.add(new DataProfile.Builder()
-                    .setApnSetting(buildDefaultApnSetting("DEFAULT IMS", "ims",
-                            ApnSetting.TYPE_IMS))
-                    .setTrafficDescriptor(new TrafficDescriptor("ims", null))
-                    .build());
-            log("Added default IMS data profile.");
-        }
-
         // Check if any of the profile already supports EIMS, if not, add the default one.
         dataProfile = profiles.stream()
                 .filter(dp -> dp.canSatisfy(NetworkCapabilities.NET_CAPABILITY_EIMS))
@@ -302,6 +337,10 @@
 
         dedupeDataProfiles(profiles);
 
+        if (mDataConfigManager.isApnConfigAnomalyReportEnabled()) {
+            checkDataProfiles(profiles);
+        }
+
         log("Found " + profiles.size() + " data profiles. profiles = " + profiles);
 
         boolean profilesChanged = false;
@@ -323,7 +362,7 @@
         }
 
         updateDataProfilesAtModem();
-        updateInitialAttachDataProfileAtModem();
+        updateInitialAttachDataProfileAtModem(forceUpdateIa);
 
         if (profilesChanged) {
             mDataProfileManagerCallbacks.forEach(callback -> callback.invokeFromExecutor(
@@ -365,23 +404,38 @@
     /**
      * 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) {
-        // If there is already a preferred data profile set, then we don't need to do anything.
-        if (mPreferredDataProfile != null) return;
+    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);
+        }
 
-        // 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.
+        // Update a working internet data profile as a future candidate for preferred data profile
+        // after APNs are reset to default
+        mLastInternetDataProfile = defaultProfile;
 
-        // 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);
+        // 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);
-        updateDataProfiles();
+        setPreferredDataProfile(defaultProfile);
+        updateDataProfiles(ONLY_UPDATE_IA_IF_CHANGED);
     }
 
     /**
@@ -434,7 +488,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);
@@ -465,6 +519,16 @@
                 if (preferredDataProfile != null) {
                     // Save the carrier specified preferred data profile into database
                     setPreferredDataProfile(preferredDataProfile);
+                } else {
+                    preferredDataProfile = mAllDataProfiles.stream()
+                            .filter(dp -> areDataProfilesSharingApn(dp, mLastInternetDataProfile))
+                            .findFirst()
+                            .orElse(null);
+                    if (preferredDataProfile != null) {
+                        log("updatePreferredDataProfile: preferredDB is empty and no carrier "
+                                + "default configured, setting preferred to be prev internet DP.");
+                        setPreferredDataProfile(preferredDataProfile);
+                    }
                 }
             }
         } else {
@@ -493,8 +557,10 @@
      * Some carriers might explicitly require that using "user-added" APN for initial
      * attach. In this case, exception can be configured through
      * {@link CarrierConfigManager#KEY_ALLOWED_INITIAL_ATTACH_APN_TYPES_STRING_ARRAY}.
+     *
+     * @param forceUpdateIa If {@code true}, we should always send IA again to modem.
      */
-    private void updateInitialAttachDataProfileAtModem() {
+    private void updateInitialAttachDataProfileAtModem(boolean forceUpdateIa) {
         DataProfile initialAttachDataProfile = null;
 
         // Sort the data profiles so the preferred data profile is at the beginning.
@@ -510,11 +576,12 @@
             if (initialAttachDataProfile != null) break;
         }
 
-        if (!Objects.equals(mInitialAttachDataProfile, initialAttachDataProfile)) {
+        if (forceUpdateIa || !Objects.equals(mInitialAttachDataProfile, initialAttachDataProfile)) {
             mInitialAttachDataProfile = initialAttachDataProfile;
-            logl("Initial attach data profile updated as " + mInitialAttachDataProfile);
+            logl("Initial attach data profile updated as " + mInitialAttachDataProfile
+                    + " or forceUpdateIa= " + forceUpdateIa);
             // TODO: Push the null data profile to modem on new AIDL HAL. Modem should clear the IA
-            //  APN.
+            //  APN, tracking for U b/227579876, now using forceUpdateIa which always push to modem
             if (mInitialAttachDataProfile != null) {
                 mWwanDataServiceManager.setInitialAttachApn(mInitialAttachDataProfile,
                         mPhone.getServiceState().getDataRoamingFromRegistration(), null);
@@ -557,14 +624,18 @@
      *
      * @param networkRequest The network request.
      * @param networkType The current data network type.
+     * @param ignorePermanentFailure {@code true} to ignore {@link ApnSetting#getPermanentFailed()}.
+     * This should be set to true for condition-based retry/setup.
      * @return The data profile. {@code null} if can't find any satisfiable data profile.
      */
     public @Nullable DataProfile getDataProfileForNetworkRequest(
-            @NonNull TelephonyNetworkRequest networkRequest, @NetworkType int networkType) {
+            @NonNull TelephonyNetworkRequest networkRequest, @NetworkType int networkType,
+            boolean ignorePermanentFailure) {
         ApnSetting apnSetting = null;
         if (networkRequest.hasAttribute(TelephonyNetworkRequest
                 .CAPABILITY_ATTRIBUTE_APN_SETTING)) {
-            apnSetting = getApnSettingForNetworkRequest(networkRequest, networkType);
+            apnSetting = getApnSettingForNetworkRequest(networkRequest, networkType,
+                    ignorePermanentFailure);
         }
 
         TrafficDescriptor.Builder trafficDescriptorBuilder = new TrafficDescriptor.Builder();
@@ -623,25 +694,41 @@
      *
      * @param networkRequest The network request.
      * @param networkType The current data network type.
+     * @param ignorePermanentFailure {@code true} to ignore {@link ApnSetting#getPermanentFailed()}.
+     * This should be set to true for condition-based retry/setup.
      * @return The APN setting. {@code null} if can't find any satisfiable data profile.
      */
     private @Nullable ApnSetting getApnSettingForNetworkRequest(
-            @NonNull TelephonyNetworkRequest networkRequest, @NetworkType int networkType) {
+            @NonNull TelephonyNetworkRequest networkRequest, @NetworkType int networkType,
+            boolean ignorePermanentFailure) {
         if (!networkRequest.hasAttribute(
                 TelephonyNetworkRequest.CAPABILITY_ATTRIBUTE_APN_SETTING)) {
             loge("Network request does not have APN setting attribute.");
             return null;
         }
 
+        // If the preferred data profile can be used, always use it if it can satisfy the network
+        // request with current network type (even though it's been marked as permanent failed.)
+        if (mPreferredDataProfile != null
+                && networkRequest.canBeSatisfiedBy(mPreferredDataProfile)
+                && mPreferredDataProfile.getApnSetting() != null
+                && mPreferredDataProfile.getApnSetting().canSupportNetworkType(networkType)) {
+            if (ignorePermanentFailure || !mPreferredDataProfile.getApnSetting()
+                    .getPermanentFailed()) {
+                return mPreferredDataProfile.getApnSetting();
+            }
+            log("The preferred data profile is permanently failed. Only condition based retry "
+                    + "can happen.");
+            return null;
+        }
+
         // Filter out the data profile that can't satisfy the request.
         // Preferred data profile should be returned in the top of the list.
         List<DataProfile> dataProfiles = mAllDataProfiles.stream()
                 .filter(networkRequest::canBeSatisfiedBy)
-                // Put the preferred data profile at the top of the list, then the longest time
-                // hasn't used data profile will be in the front so all the data profiles can be
-                // tried.
-                .sorted(Comparator.comparing((DataProfile dp) -> !dp.equals(mPreferredDataProfile))
-                        .thenComparingLong(DataProfile::getLastSetupTimestamp))
+                // The longest time hasn't used data profile will be in the front so all the data
+                // profiles can be tried.
+                .sorted(Comparator.comparing(DataProfile::getLastSetupTimestamp))
                 .collect(Collectors.toList());
         for (DataProfile dataProfile : dataProfiles) {
             logv("Satisfied profile: " + dataProfile + ", last setup="
@@ -676,17 +763,39 @@
             return null;
         }
 
+        // Check if data profiles are permanently failed.
+        dataProfiles = dataProfiles.stream()
+                .filter(dp -> ignorePermanentFailure || !dp.getApnSetting().getPermanentFailed())
+                .collect(Collectors.toList());
+        if (dataProfiles.size() == 0) {
+            log("The suitable data profiles are all in permanent failed state.");
+            return null;
+        }
+
         return dataProfiles.get(0).getApnSetting();
     }
 
     /**
-     * Check if the data profile is the preferred data profile.
+     * Check if the data profile is essentially the preferred data profile. The non-essential
+     * elements include e.g.APN Id.
      *
      * @param dataProfile The data profile to check.
-     * @return {@code true} if the data profile is the preferred data profile.
+     * @return {@code true} if the data profile is essentially the preferred data profile.
      */
     public boolean isDataProfilePreferred(@NonNull DataProfile dataProfile) {
-        return dataProfile.equals(mPreferredDataProfile);
+        return areDataProfilesSharingApn(dataProfile, mPreferredDataProfile);
+    }
+
+    /**
+     * @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));
     }
 
     /**
@@ -707,19 +816,7 @@
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
                         .build(), mPhone);
-        return getDataProfileForNetworkRequest(networkRequest, networkType) != 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;
+        return getDataProfileForNetworkRequest(networkRequest, networkType, true) != null;
     }
 
     /**
@@ -750,6 +847,72 @@
     }
 
     /**
+     * Trigger anomaly report if APN Setting contains invalid info.
+     *
+     * @param setting The Apn setting to be checked.
+     */
+    private void checkApnSetting(@NonNull ApnSetting setting) {
+        if (setting.canHandleType(ApnSetting.TYPE_MMS)) {
+            if (setting.getMmsc() == null) {
+                reportAnomaly("MMS is supported but no MMSC configured " + setting,
+                        "9af73e18-b523-4dc5-adab-19d86c6a3685");
+            } else if (!setting.getMmsc().toString().matches("^https?:\\/\\/.+")) {
+                reportAnomaly("Apn config mmsc should start with http but is "
+                                + setting.getMmsc(),
+                        "9af73e18-b523-4dc5-adab-ec754d959d4d");
+            }
+            if (!TextUtils.isEmpty(setting.getMmsProxyAddressAsString())
+                    && setting.getMmsProxyAddressAsString().matches("^https?:\\/\\/.+")) {
+                reportAnomaly("Apn config mmsc_proxy should NOT start with http but is "
+                                + setting.getMmsc(), "9af73e18-b523-4dc5-adab-ec754d959d4d");
+            }
+        }
+    }
+
+    /**
+     * Trigger anomaly report if any two Apn Settings share the same APN name while having
+     * overlapped network types.
+     *
+     * @param profiles The list of data profiles to be checked.
+     */
+    private void checkDataProfiles(List<DataProfile> profiles) {
+        for (int i = 0; i < profiles.size(); i++) {
+            ApnSetting a = profiles.get(i).getApnSetting();
+            if (a == null) continue;
+            if (// Lingering network is not the default and doesn't cover all the regular networks
+                    (int) TelephonyManager.NETWORK_TYPE_BITMASK_UNKNOWN
+                    != a.getLingeringNetworkTypeBitmask()
+                            && (a.getNetworkTypeBitmask() | a.getLingeringNetworkTypeBitmask())
+                    != a.getLingeringNetworkTypeBitmask()) {
+                reportAnomaly("Apn[" + a.getApnName() + "] network "
+                                + TelephonyManager.convertNetworkTypeBitmaskToString(
+                                        a.getNetworkTypeBitmask()) + " should be a subset of "
+                                + "the lingering network "
+                                + TelephonyManager.convertNetworkTypeBitmaskToString(
+                                a.getLingeringNetworkTypeBitmask()),
+                        "9af73e18-b523-4dc5-adab-4bb24355d838");
+            }
+            for (int j = i + 1; j < profiles.size(); j++) {
+                ApnSetting b = profiles.get(j).getApnSetting();
+                if (b == null) continue;
+                String apnNameA = a.getApnName();
+                String apnNameB = b.getApnName();
+                if (TextUtils.equals(apnNameA, apnNameB)
+                        // TelephonyManager.NETWORK_TYPE_BITMASK_UNKNOWN means all network types
+                        && (a.getNetworkTypeBitmask()
+                        == (int) TelephonyManager.NETWORK_TYPE_BITMASK_UNKNOWN
+                        || b.getNetworkTypeBitmask()
+                        == (int) TelephonyManager.NETWORK_TYPE_BITMASK_UNKNOWN
+                        || (a.getNetworkTypeBitmask() & b.getNetworkTypeBitmask()) != 0)) {
+                    reportAnomaly("Found overlapped network type under the APN name "
+                                    + a.getApnName(),
+                            "9af73e18-b523-4dc5-adab-4bb24555d839");
+                }
+            }
+        }
+    }
+
+    /**
      * Merge two data profiles if possible.
      *
      * @param dp1 Data profile 1 to be merged.
@@ -806,6 +969,9 @@
                 ? apn1.getMtuV4() : apn2.getMtuV4());
         apnBuilder.setMtuV6(apn2.getMtuV6() <= ApnSetting.UNSET_MTU
                 ? apn1.getMtuV6() : apn2.getMtuV6());
+        // legacy properties that don't matter
+        apnBuilder.setMvnoType(apn1.getMvnoType());
+        apnBuilder.setMvnoMatchData(apn1.getMvnoMatchData());
 
         // The following fields in apn1 and apn2 should be the same, otherwise ApnSetting.similar()
         // should fail earlier.
@@ -820,8 +986,6 @@
         apnBuilder.setMaxConns(apn1.getMaxConns());
         apnBuilder.setWaitTime(apn1.getWaitTime());
         apnBuilder.setMaxConnsTime(apn1.getMaxConnsTime());
-        apnBuilder.setMvnoType(apn1.getMvnoType());
-        apnBuilder.setMvnoMatchData(apn1.getMvnoMatchData());
         apnBuilder.setApnSetId(apn1.getApnSetId());
         apnBuilder.setCarrierId(apn1.getCarrierId());
         apnBuilder.setSkip464Xlat(apn1.getSkip464Xlat());
@@ -834,39 +998,43 @@
     }
 
     /**
-     * Get data profile by APN name and/or traffic descriptor.
+     * Check if the provided data profile is still compatible with the current environment. Note
+     * this method ignores APN id check and traffic descriptor check. A data profile with traffic
+     * descriptor only can always be used in any condition.
      *
-     * @param apnName APN name.
-     * @param trafficDescriptor Traffic descriptor.
-     *
-     * @return Data profile by APN name and/or traffic descriptor. Either one of APN name or
-     * traffic descriptor should be provided. {@code null} if data profile is not found.
+     * @param dataProfile The data profile to check.
+     * @return {@code true} if the provided data profile can be still used in current environment.
      */
-    public @Nullable DataProfile getDataProfile(@Nullable String apnName,
-            @Nullable TrafficDescriptor trafficDescriptor) {
-        if (apnName == null && trafficDescriptor == null) return null;
-
-        List<DataProfile> dataProfiles = mAllDataProfiles;
-
-        // Check if any existing data profile has the same traffic descriptor.
-        if (trafficDescriptor != null) {
-            dataProfiles = mAllDataProfiles.stream()
-                    .filter(dp -> trafficDescriptor.equals(dp.getTrafficDescriptor()))
-                    .collect(Collectors.toList());
+    public boolean isDataProfileCompatible(@NonNull DataProfile dataProfile) {
+        if (dataProfile == null) {
+            return false;
         }
 
-        // Check if any existing data profile has the same APN name.
-        if (apnName != null) {
-            dataProfiles = dataProfiles.stream()
-                    .filter(dp -> dp.getApnSetting() != null
-                            && (dp.getApnSetting().getApnSetId()
-                            == Telephony.Carriers.MATCH_ALL_APN_SET_ID
-                            || dp.getApnSetting().getApnSetId() == mPreferredDataProfileSetId))
-                    .filter(dp -> apnName.equals(dp.getApnSetting().getApnName()))
-                    .collect(Collectors.toList());
+        if (dataProfile.getApnSetting() == null && dataProfile.getTrafficDescriptor() != null) {
+            // A traffic descriptor only data profile can be always used. Traffic descriptors are
+            // always generated on the fly instead loaded from the database.
+            return true;
         }
 
-        return dataProfiles.isEmpty() ? null : dataProfiles.get(0);
+        // Check the APN from the profile is compatible and matches preferred data profile set id.
+        return mAllDataProfiles.stream()
+                .filter(dp -> dp.getApnSetting() != null
+                        && (dp.getApnSetting().getApnSetId()
+                        == Telephony.Carriers.MATCH_ALL_APN_SET_ID
+                        || dp.getApnSetting().getApnSetId() == mPreferredDataProfileSetId))
+                .anyMatch(dp -> areDataProfilesSharingApn(dataProfile, dp));
+    }
+
+    /**
+     * @return {@code true} if both data profiles' APN setting are non-null and essentially the same
+     * (non-essential elements include e.g.APN Id).
+     */
+    public boolean areDataProfilesSharingApn(@Nullable DataProfile a, @Nullable DataProfile b) {
+        return a != null
+                && b != null
+                && a.getApnSetting() != null
+                && a.getApnSetting().equals(b.getApnSetting(),
+                mPhone.getServiceState().getDataRoamingFromRegistration());
     }
 
     /**
@@ -888,6 +1056,17 @@
     }
 
     /**
+     * Trigger the anomaly report with the specified UUID.
+     *
+     * @param anomalyMsg Description of the event
+     * @param uuid UUID associated with that event
+     */
+    private void reportAnomaly(@NonNull String anomalyMsg, @NonNull String uuid) {
+        logl(anomalyMsg);
+        AnomalyReporter.reportAnomaly(UUID.fromString(uuid), anomalyMsg, mPhone.getCarrierId());
+    }
+
+    /**
      * Log debug messages.
      * @param s debug messages
      */
@@ -945,9 +1124,16 @@
         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));
+        pw.println("Permanent failed profiles=");
+        pw.increaseIndent();
+        mAllDataProfiles.stream()
+                .filter(dp -> dp.getApnSetting() != null && dp.getApnSetting().getPermanentFailed())
+                .forEach(pw::println);
+        pw.decreaseIndent();
 
         pw.println("Local logs:");
         pw.increaseIndent();
diff --git a/src/java/com/android/internal/telephony/data/DataRetryManager.java b/src/java/com/android/internal/telephony/data/DataRetryManager.java
index da86cf4..518689a 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;
@@ -46,6 +52,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.data.DataConfigManager.DataConfigManagerCallback;
 import com.android.internal.telephony.data.DataNetworkController.DataNetworkControllerCallback;
 import com.android.internal.telephony.data.DataNetworkController.NetworkRequestList;
 import com.android.internal.telephony.data.DataProfileManager.DataProfileManagerCallback;
@@ -55,7 +62,6 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
 import java.util.Objects;
@@ -64,6 +70,7 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * DataRetryManager manages data network setup retry and its configurations.
@@ -71,8 +78,10 @@
 public class DataRetryManager extends Handler {
     private static final boolean VDBG = false;
 
-    /** Event for data config updated. */
-    private static final int EVENT_DATA_CONFIG_UPDATED = 1;
+    /** 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;
@@ -100,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 = {
@@ -145,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.
@@ -316,10 +334,6 @@
                 }
             }
 
-            if (mMaxRetries == 0) {
-                mRetryIntervalsMillis = Collections.emptyList();
-            }
-
             if (mMaxRetries < 0) {
                 throw new IllegalArgumentException("Max retries should not be less than 0. "
                         + "mMaxRetries=" + mMaxRetries);
@@ -360,39 +374,45 @@
     }
 
     /**
-     * Represent a setup data network retry rule.
+     * Represent a rule for data setup retry.
      *
      * The syntax of the retry rule:
-     * 1. Retry based on {@link NetworkCapabilities}. Note that only APN-type network
-     * capabilities are supported.
-     * "capabilities=[netCaps1|netCaps2|...], [retry_interval=n1|n2|n3|n4...],
-     * [maximum_retries=n]"
+     * 1. Retry based on {@link NetworkCapabilities}. Note that only APN-type network capabilities
+     *    are supported. If the capabilities are not specified, then the retry rule only applies
+     *    to the current failed APN used in setup data call request.
+     * "capabilities=[netCaps1|netCaps2|...], [retry_interval=n1|n2|n3|n4...], [maximum_retries=n]"
      *
      * 2. Retry based on {@link DataFailCause}
-     * "fail_causes=[cause1|cause2|cause3|..], [retry_interval=n1|n2|n3|n4...],
-     * [maximum_retries=n]"
+     * "fail_causes=[cause1|cause2|cause3|..], [retry_interval=n1|n2|n3|n4...], [maximum_retries=n]"
      *
      * 3. Retry based on {@link NetworkCapabilities} and {@link DataFailCause}. Note that only
      *    APN-type network capabilities are supported.
      * "capabilities=[netCaps1|netCaps2|...], fail_causes=[cause1|cause2|cause3|...],
      *     [retry_interval=n1|n2|n3|n4...], [maximum_retries=n]"
      *
+     * 4. Permanent fail causes (no timer-based retry) on the current failed APN. Retry interval
+     *    is specified for retrying the next available APN.
+     * "permanent_fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|-3|65543|65547|
+     *     2252|2253|2254, retry_interval=2500"
+     *
      * For example,
      * "capabilities=eims, retry_interval=1000, maximum_retries=20" means if the attached
      * network request is emergency, then retry data network setup every 1 second for up to 20
      * times.
      *
-     * "fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|-3|2253|2254
-     * , maximum_retries=0" means for those fail causes, never retry with timers. Note that
-     * when environment changes, retry can still happen.
-     *
      * "capabilities=internet|enterprise|dun|ims|fota, retry_interval=2500|3000|"
      * "5000|10000|15000|20000|40000|60000|120000|240000|600000|1200000|1800000"
-     * "1800000, maximum_retries=20" means for those capabilities, retry happens in 2.5s, 3s,
-     * 5s, 10s, 15s, 20s, 40s, 1m, 2m, 4m, 10m, 20m, 30m, 30m, 30m, until reaching 20 retries.
+     * "1800000, maximum_retries=20" means for those capabilities, retry happens in 2.5s, 3s, 5s,
+     * 10s, 15s, 20s, 40s, 1m, 2m, 4m, 10m, 20m, 30m, 30m, 30m, until reaching 20 retries.
+     *
      */
     public static class DataSetupRetryRule extends DataRetryRule {
+        private static final String RULE_TAG_PERMANENT_FAIL_CAUSES = "permanent_fail_causes";
         private static final String RULE_TAG_CAPABILITIES = "capabilities";
+
+        /** {@code true} if this rule is for permanent fail causes. */
+        private boolean mIsPermanentFailCauseRule;
+
         /**
          * Constructor
          *
@@ -411,8 +431,23 @@
                 }
                 String key = tokens[0].trim();
                 String value = tokens[1].trim();
-                if (key.equals(RULE_TAG_CAPABILITIES)) {
-                    mNetworkCapabilities = DataUtils.getNetworkCapabilitiesFromString(value);
+                try {
+                    switch (key) {
+                        case RULE_TAG_PERMANENT_FAIL_CAUSES:
+                            mFailCauses = Arrays.stream(value.split("\\s*\\|\\s*"))
+                                    .map(String::trim)
+                                    .map(Integer::valueOf)
+                                    .collect(Collectors.toSet());
+                            mIsPermanentFailCauseRule = true;
+                            break;
+                        case RULE_TAG_CAPABILITIES:
+                            mNetworkCapabilities = DataUtils
+                                    .getNetworkCapabilitiesFromString(value);
+                            break;
+                    }
+                } catch (Exception e) {
+                    e.printStackTrace();
+                    throw new IllegalArgumentException("illegal rule " + ruleString + ", e=" + e);
                 }
             }
 
@@ -435,6 +470,13 @@
         }
 
         /**
+         * @return {@code true} if this rule is for permanent fail causes.
+         */
+        public boolean isPermanentFailCauseRule() {
+            return mIsPermanentFailCauseRule;
+        }
+
+        /**
          * Check if this rule can be matched.
          *
          * @param networkCapability The network capability for matching.
@@ -930,12 +972,17 @@
         mDataServiceManagers = dataServiceManagers;
         mDataConfigManager = dataNetworkController.getDataConfigManager();
         mDataProfileManager = dataNetworkController.getDataProfileManager();
-        mDataConfigManager.registerForConfigUpdate(this, EVENT_DATA_CONFIG_UPDATED);
+        mAlarmManager = mPhone.getContext().getSystemService(AlarmManager.class);
 
-        mDataServiceManagers.get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                .registerForApnUnthrottled(this, EVENT_DATA_PROFILE_UNTHROTTLED);
-        if (!mPhone.getAccessNetworksManager().isInLegacyMode()) {
-            mDataServiceManagers.get(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
+        mDataConfigManager.registerCallback(new DataConfigManagerCallback(this::post) {
+            @Override
+            public void onCarrierConfigChanged() {
+                DataRetryManager.this.onCarrierConfigUpdated();
+            }
+        });
+
+        for (int transport : mPhone.getAccessNetworksManager().getAvailableTransports()) {
+            mDataServiceManagers.get(transport)
                     .registerForApnUnthrottled(this, EVENT_DATA_PROFILE_UNTHROTTLED);
         }
         mDataProfileManager.registerCallback(new DataProfileManagerCallback(this::post) {
@@ -946,14 +993,44 @@
         });
         dataNetworkController.registerDataNetworkControllerCallback(
                 new DataNetworkControllerCallback(this::post) {
+                    /**
+                     * Called when data service is bound.
+                     *
+                     * @param transport The transport of the data service.
+                     */
                     @Override
                     public void onDataServiceBound(@TransportType int transport) {
                         onReset(RESET_REASON_DATA_SERVICE_BOUND);
                     }
+
+                    /**
+                     * Called when data network is connected.
+                     *
+                     * @param transport Transport for the connected network.
+                     * @param dataProfile The data profile of the connected data network.
+                     */
+                    @Override
+                    public void onDataNetworkConnected(@TransportType int transport,
+                            @NonNull DataProfile dataProfile) {
+                        DataRetryManager.this.onDataNetworkConnected(transport, dataProfile);
+                    }
                 });
         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);
@@ -964,23 +1041,18 @@
     public void handleMessage(Message msg) {
         AsyncResult ar;
         switch (msg.what) {
-            case EVENT_DATA_CONFIG_UPDATED:
-                onDataConfigUpdated();
-                break;
             case EVENT_DATA_SETUP_RETRY:
                 DataSetupRetryEntry dataSetupRetryEntry = (DataSetupRetryEntry) msg.obj;
-                Objects.requireNonNull(dataSetupRetryEntry);
-                mDataRetryManagerCallbacks.forEach(callback -> callback.invokeFromExecutor(
-                        () -> callback.onDataNetworkSetupRetry(dataSetupRetryEntry)));
+                if (!isRetryCancelled(dataSetupRetryEntry)) {
+                    mDataRetryManagerCallbacks.forEach(callback -> callback.invokeFromExecutor(
+                            () -> callback.onDataNetworkSetupRetry(dataSetupRetryEntry)));
+                }
                 break;
             case EVENT_DATA_HANDOVER_RETRY:
                 DataHandoverRetryEntry dataHandoverRetryEntry = (DataHandoverRetryEntry) msg.obj;
-                Objects.requireNonNull(dataHandoverRetryEntry);
-                if (mDataRetryEntries.contains(dataHandoverRetryEntry)) {
+                if (!isRetryCancelled(dataHandoverRetryEntry)) {
                     mDataRetryManagerCallbacks.forEach(callback -> callback.invokeFromExecutor(
                             () -> callback.onDataNetworkHandoverRetry(dataHandoverRetryEntry)));
-                } else {
-                    log("Handover was cancelled earlier. " + dataHandoverRetryEntry);
                 }
                 break;
             case EVENT_RADIO_ON:
@@ -1003,7 +1075,7 @@
                 } else if (ar.result instanceof DataProfile) {
                     dataProfile = (DataProfile) ar.result;
                 }
-                onDataProfileUnthrottled(dataProfile, apn, transport, true);
+                onDataProfileUnthrottled(dataProfile, apn, transport, true, true);
                 break;
             case EVENT_CANCEL_PENDING_HANDOVER_RETRY:
                 onCancelPendingHandoverRetry((DataNetwork) msg.obj);
@@ -1014,9 +1086,21 @@
     }
 
     /**
-     * Called when data config is updated.
+     * @param retryEntry The retry entry to check.
+     * @return {@code true} if the retry is null or not in RETRY_STATE_NOT_RETRIED state.
      */
-    private void onDataConfigUpdated() {
+    private boolean isRetryCancelled(@Nullable DataRetryEntry retryEntry) {
+        if (retryEntry != null && retryEntry.getState() == DataRetryEntry.RETRY_STATE_NOT_RETRIED) {
+            return false;
+        }
+        log("Retry was removed earlier. " + retryEntry);
+        return true;
+    }
+
+    /**
+     * Called when carrier config is updated.
+     */
+    private void onCarrierConfigUpdated() {
         onReset(RESET_REASON_DATA_CONFIG_CHANGED);
         mDataSetupRetryRuleList = mDataConfigManager.getDataSetupRetryRules();
         mDataHandoverRetryRuleList = mDataConfigManager.getDataHandoverRetryRules();
@@ -1025,6 +1109,21 @@
     }
 
     /**
+     * Called when data network is connected.
+     *
+     * @param transport Transport for the connected network.
+     * @param dataProfile The data profile of the connected data network.
+     */
+    public void onDataNetworkConnected(@TransportType int transport,
+            @NonNull DataProfile dataProfile) {
+        if (dataProfile.getApnSetting() != null) {
+            dataProfile.getApnSetting().setPermanentFailed(false);
+        }
+
+        onDataProfileUnthrottled(dataProfile, null, transport, true, false);
+    }
+
+    /**
      * Evaluate if data setup retry is needed or not. If needed, retry will be scheduled
      * automatically after evaluation.
      *
@@ -1062,6 +1161,7 @@
             // ThrottleStatus is just for API backwards compatibility reason.
             updateThrottleStatus(dataProfile, requestList, null,
                     ThrottleStatus.RETRY_TYPE_NEW_CONNECTION, transport, Long.MAX_VALUE);
+            return;
         } else if (retryDelayMillis != DataCallResponse.RETRY_DURATION_UNDEFINED) {
             // Network specifically asks retry the previous data profile again.
             DataSetupRetryEntry dataSetupRetryEntry = new DataSetupRetryEntry.Builder<>()
@@ -1075,67 +1175,90 @@
                     ThrottleStatus.RETRY_TYPE_NEW_CONNECTION, transport,
                     dataSetupRetryEntry.retryElapsedTime);
             schedule(dataSetupRetryEntry);
-        } else {
-            // Network did not suggest any retry. Use the configured rules to perform retry.
-            logv("mDataSetupRetryRuleList=" + mDataSetupRetryRuleList);
+            return;
+        }
 
-            // Support the legacy permanent failure configuration
-            if (DataFailCause.isPermanentFailure(mPhone.getContext(), cause, mPhone.getSubId())) {
-                log("Stopped timer-based retry. cause=" + DataFailCause.toString(cause));
+        // Network did not suggest any retry. Use the configured rules to perform retry.
+        logv("mDataSetupRetryRuleList=" + mDataSetupRetryRuleList);
+
+        boolean retryScheduled = false;
+        List<NetworkRequestList> groupedNetworkRequestLists =
+                DataUtils.getGroupedNetworkRequestList(requestList);
+        for (DataSetupRetryRule retryRule : mDataSetupRetryRuleList) {
+            if (retryRule.isPermanentFailCauseRule() && retryRule.getFailCauses().contains(cause)) {
+                if (dataProfile.getApnSetting() != null) {
+                    dataProfile.getApnSetting().setPermanentFailed(true);
+
+                    // It seems strange to have retry timer in permanent failure rule, but since
+                    // in this case permanent failure is only applicable to the failed profile, so
+                    // we need to see if other profile can be selected for next data setup.
+                    log("Marked " + dataProfile.getApnSetting().getApnName() + " permanently "
+                            + "failed, but still schedule retry to see if another data profile "
+                            + "can be used for setup data.");
+                    // Schedule a data retry to see if another data profile could be selected.
+                    // If the same data profile is selected again, since it's marked as
+                    // permanent failure, it won't be used for setup data call.
+                    schedule(new DataSetupRetryEntry.Builder<>()
+                            .setRetryDelay(retryRule.getRetryIntervalsMillis().get(0))
+                            .setAppliedRetryRule(retryRule)
+                            .setSetupRetryType(DataSetupRetryEntry.RETRY_TYPE_NETWORK_REQUESTS)
+                            .setTransport(transport)
+                            .setNetworkRequestList(requestList)
+                            .build());
+                } else {
+                    // For TD-based data profile, do not do anything for now. Should expand this in
+                    // the future if needed.
+                    log("Stopped timer-based retry for TD-based data profile. Will retry only when "
+                            + "environment changes.");
+                }
                 return;
             }
-
-            boolean retryScheduled = false;
-            List<NetworkRequestList> groupedNetworkRequestLists =
-                    DataUtils.getGroupedNetworkRequestList(requestList);
             for (NetworkRequestList networkRequestList : groupedNetworkRequestLists) {
                 int capability = networkRequestList.get(0).getApnTypeNetworkCapability();
-
-                for (DataSetupRetryRule retryRule : mDataSetupRetryRuleList) {
-                    if (retryRule.canBeMatched(capability, cause)) {
-                        // Check if there is already a similar network request retry scheduled.
-                        if (isSimilarNetworkRequestRetryScheduled(
-                                networkRequestList.get(0), transport)) {
-                            log(networkRequestList.get(0) + " already had similar retry "
-                                    + "scheduled.");
-                            return;
-                        }
-
-                        int failedCount = getRetryFailedCount(capability, retryRule);
-                        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 "
-                                    + DataUtils.networkCapabilityToString(capability)
-                                    + ". Condition-based retry will still happen when condition "
-                                    + "changes.");
-                            return;
-                        }
-
-                        retryDelayMillis = retryRule.getRetryIntervalsMillis().get(
-                                Math.min(failedCount, retryRule
-                                        .getRetryIntervalsMillis().size() - 1));
-
-                        // Schedule a data retry.
-                        schedule(new DataSetupRetryEntry.Builder<>()
-                                .setRetryDelay(retryDelayMillis)
-                                .setAppliedRetryRule(retryRule)
-                                .setSetupRetryType(DataSetupRetryEntry.RETRY_TYPE_NETWORK_REQUESTS)
-                                .setTransport(transport)
-                                .setNetworkRequestList(networkRequestList)
-                                .build());
-                        retryScheduled = true;
-                        break;
+                if (retryRule.canBeMatched(capability, cause)) {
+                    // Check if there is already a similar network request retry scheduled.
+                    if (isSimilarNetworkRequestRetryScheduled(
+                            networkRequestList.get(0), transport)) {
+                        log(networkRequestList.get(0) + " already had similar retry "
+                                + "scheduled.");
+                        return;
                     }
+
+                    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 on "
+                                + AccessNetworkConstants.transportTypeToString(transport)
+                                + ". Stopped timer-based data retry for "
+                                + DataUtils.networkCapabilityToString(capability)
+                                + ". Condition-based retry will still happen when condition "
+                                + "changes.");
+                        return;
+                    }
+
+                    retryDelayMillis = retryRule.getRetryIntervalsMillis().get(
+                            Math.min(failedCount, retryRule
+                                    .getRetryIntervalsMillis().size() - 1));
+
+                    // Schedule a data retry.
+                    schedule(new DataSetupRetryEntry.Builder<>()
+                            .setRetryDelay(retryDelayMillis)
+                            .setAppliedRetryRule(retryRule)
+                            .setSetupRetryType(DataSetupRetryEntry.RETRY_TYPE_NETWORK_REQUESTS)
+                            .setTransport(transport)
+                            .setNetworkRequestList(networkRequestList)
+                            .build());
+                    retryScheduled = true;
+                    break;
                 }
             }
+        }
 
-            if (!retryScheduled) {
-                log("onEvaluateDataSetupRetry: Did not match any retry rule. Stop timer-based "
-                        + "retry.");
-            }
+        if (!retryScheduled) {
+            log("onEvaluateDataSetupRetry: Did not match any retry rule. Stop timer-based "
+                    + "retry.");
         }
     }
 
@@ -1211,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));
@@ -1224,7 +1365,7 @@
             DataProfile dataProfile = dataThrottlingEntry.dataProfile;
             String apn = dataProfile.getApnSetting() != null
                     ? dataProfile.getApnSetting().getApnName() : null;
-            onDataProfileUnthrottled(dataProfile, apn, dataThrottlingEntry.transport, false);
+            onDataProfileUnthrottled(dataProfile, apn, dataThrottlingEntry.transport, false, true);
         }
 
         mDataThrottlingEntries.clear();
@@ -1263,16 +1404,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);
@@ -1304,20 +1447,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));
+        }
     }
 
     /**
@@ -1340,15 +1513,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);
 
@@ -1383,10 +1561,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 retry after unthrottling.
      */
     private void onDataProfileUnthrottled(@Nullable DataProfile dataProfile, @Nullable String apn,
-            int transport, boolean remove) {
-        log("onDataProfileUnthrottled: data profile=" + dataProfile + ", apn=" + apn
+            @TransportType int transport, boolean remove, boolean retry) {
+        log("onDataProfileUnthrottled: dataProfile=" + dataProfile + ", apn=" + apn
                 + ", transport=" + AccessNetworkConstants.transportTypeToString(transport)
                 + ", remove=" + remove);
 
@@ -1398,18 +1577,18 @@
             // 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.
-            final DataProfile dp = mDataProfileManager.getDataProfile(
-                    dataProfile.getApnSetting() != null
-                            ? dataProfile.getApnSetting().getApnName() : null,
-                    dataProfile.getTrafficDescriptor());
-            log("onDataProfileUnthrottled: getDataProfile=" + dp);
-            if (dp != null) {
-                dataUnthrottlingEntries = mDataThrottlingEntries.stream()
-                        .filter(entry -> entry.expirationTimeMillis > now
-                                && entry.dataProfile.equals(dp)
-                                && entry.transport == transport)
-                        .collect(Collectors.toList());
+            Stream<DataThrottlingEntry> stream = mDataThrottlingEntries.stream();
+            stream = stream.filter(entry -> entry.expirationTimeMillis > now);
+            if (dataProfile.getApnSetting() != null) {
+                stream = stream
+                        .filter(entry -> entry.dataProfile.getApnSetting() != null)
+                        .filter(entry -> entry.dataProfile.getApnSetting().getApnName()
+                                .equals(dataProfile.getApnSetting().getApnName()));
             }
+            stream = stream.filter(entry -> Objects.equals(entry.dataProfile.getTrafficDescriptor(),
+                    dataProfile.getTrafficDescriptor()));
+
+            dataUnthrottlingEntries = stream.collect(Collectors.toList());
         } else if (apn != null) {
             // For HIDL 1.6 or below
             dataUnthrottlingEntries = mDataThrottlingEntries.stream()
@@ -1441,6 +1620,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)
@@ -1455,23 +1635,30 @@
         mDataRetryManagerCallbacks.forEach(callback -> callback.invokeFromExecutor(
                 () -> callback.onThrottleStatusChanged(throttleStatusList)));
 
+        if (unthrottledProfile != null) {
+            // cancel pending retries since we will soon schedule an immediate retry
+            cancelRetriesForDataProfile(unthrottledProfile, transport);
+        }
+
         logl("onDataProfileUnthrottled: Removing the following throttling entries. "
                 + dataUnthrottlingEntries);
-        for (DataThrottlingEntry entry : dataUnthrottlingEntries) {
-            if (entry.retryType == ThrottleStatus.RETRY_TYPE_NEW_CONNECTION) {
+        if (retry) {
+            for (DataThrottlingEntry entry : dataUnthrottlingEntries) {
                 // Immediately retry after unthrottling.
-                schedule(new DataSetupRetryEntry.Builder<>()
-                        .setDataProfile(entry.dataProfile)
-                        .setTransport(entry.transport)
-                        .setSetupRetryType(DataSetupRetryEntry.RETRY_TYPE_DATA_PROFILE)
-                        .setNetworkRequestList(entry.networkRequestList)
-                        .setRetryDelay(0)
-                        .build());
-            } else if (entry.retryType == ThrottleStatus.RETRY_TYPE_HANDOVER) {
-                schedule(new DataHandoverRetryEntry.Builder<>()
-                        .setDataNetwork(entry.dataNetwork)
-                        .setRetryDelay(0)
-                        .build());
+                if (entry.retryType == ThrottleStatus.RETRY_TYPE_NEW_CONNECTION) {
+                    schedule(new DataSetupRetryEntry.Builder<>()
+                            .setDataProfile(entry.dataProfile)
+                            .setTransport(entry.transport)
+                            .setSetupRetryType(DataSetupRetryEntry.RETRY_TYPE_DATA_PROFILE)
+                            .setNetworkRequestList(entry.networkRequestList)
+                            .setRetryDelay(0)
+                            .build());
+                } else if (entry.retryType == ThrottleStatus.RETRY_TYPE_HANDOVER) {
+                    schedule(new DataHandoverRetryEntry.Builder<>()
+                            .setDataNetwork(entry.dataNetwork)
+                            .setRetryDelay(0)
+                            .build());
+                }
             }
         }
         if (remove) {
@@ -1480,6 +1667,34 @@
     }
 
     /**
+     * Cancel pending retries that uses the specified data profile, with specified target transport.
+     *
+     * @param dataProfile The data profile to cancel.
+     * @param transport The target {@link TransportType} on which the retry to cancel.
+     */
+    private void cancelRetriesForDataProfile(@NonNull DataProfile dataProfile,
+            @TransportType int transport) {
+        logl("cancelRetriesForDataProfile: Canceling pending retries for " + dataProfile);
+        mDataRetryEntries.stream()
+                .filter(entry -> {
+                    if (entry.getState() == DataRetryEntry.RETRY_STATE_NOT_RETRIED) {
+                        if (entry instanceof DataSetupRetryEntry) {
+                            DataSetupRetryEntry retryEntry = (DataSetupRetryEntry) entry;
+                            return dataProfile.equals(retryEntry.dataProfile)
+                                    && transport == retryEntry.transport;
+                        } else if (entry instanceof DataHandoverRetryEntry) {
+                            DataHandoverRetryEntry retryEntry = (DataHandoverRetryEntry) entry;
+                            return dataProfile.equals(retryEntry.dataNetwork.getDataProfile());
+                        }
+                    }
+                    return false;
+                })
+                .forEach(entry -> entry.setState(DataRetryEntry.RETRY_STATE_CANCELLED));
+    }
+
+
+
+    /**
      * Check if there is any similar network request scheduled to retry. The definition of similar
      * is that network requests have same APN capability and on the same transport.
      *
@@ -1489,12 +1704,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);
@@ -1517,23 +1734,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.
@@ -1563,9 +1763,11 @@
      * @param dataNetwork The data network that was originally scheduled for handover retry.
      */
     private void onCancelPendingHandoverRetry(@NonNull DataNetwork dataNetwork) {
-        mDataRetryEntries.removeIf(entry -> entry instanceof DataHandoverRetryEntry
-                && ((DataHandoverRetryEntry) entry).dataNetwork == dataNetwork);
-        mDataThrottlingEntries.removeIf(entry -> entry.dataNetwork == dataNetwork);
+        mDataRetryEntries.stream()
+                .filter(entry -> entry instanceof DataHandoverRetryEntry
+                        && ((DataHandoverRetryEntry) entry).dataNetwork == dataNetwork
+                        && entry.getState() == DataRetryEntry.RETRY_STATE_NOT_RETRIED)
+                .forEach(entry -> entry.setState(DataRetryEntry.RETRY_STATE_CANCELLED));
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/data/DataServiceManager.java b/src/java/com/android/internal/telephony/data/DataServiceManager.java
index 41190bf..2733aff 100644
--- a/src/java/com/android/internal/telephony/data/DataServiceManager.java
+++ b/src/java/com/android/internal/telephony/data/DataServiceManager.java
@@ -22,11 +22,9 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -121,22 +119,6 @@
 
     private List<DataCallResponse> mLastDataCallResponseList = Collections.EMPTY_LIST;
 
-    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(action)
-                    && mPhone.getPhoneId() == intent.getIntExtra(
-                    CarrierConfigManager.EXTRA_SLOT_INDEX, 0)) {
-                // We should wait for carrier config changed event because the target binding
-                // package name can come from the carrier config. Note that we still get this event
-                // even when SIM is absent.
-                if (DBG) log("Carrier config changed. Try to bind data service.");
-                sendEmptyMessage(EVENT_BIND_DATA_SERVICE);
-            }
-        }
-    };
-
     private class DataServiceManagerDeathRecipient implements IBinder.DeathRecipient {
         @Override
         public void binderDied() {
@@ -409,16 +391,18 @@
                 Context.LEGACY_PERMISSION_SERVICE);
         mAppOps = (AppOpsManager) phone.getContext().getSystemService(Context.APP_OPS_SERVICE);
 
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        try {
-            Context contextAsUser = phone.getContext().createPackageContextAsUser(
-                    phone.getContext().getPackageName(), 0, UserHandle.ALL);
-            contextAsUser.registerReceiver(mBroadcastReceiver, intentFilter,
-                    null /* broadcastPermission */, null);
-        } catch (PackageManager.NameNotFoundException e) {
-            loge("Package name not found: " + e.getMessage());
-        }
+        // Callback is executed in handler thread to directly handle config change.
+        mCarrierConfigManager.registerCarrierConfigChangeListener(this::post,
+                (slotIndex, subId, carrierId, specificCarrierId) -> {
+                    if (slotIndex == mPhone.getPhoneId()) {
+                        // We should wait for carrier config changed event because the
+                        // target binding package name can come from the carrier config.
+                        // Note that we still get this event even when SIM is absent.
+                        if (DBG) log("Carrier config changed. Try to bind data service.");
+                        rebindDataService();
+                    }
+                });
+
         PhoneConfigurationManager.registerForMultiSimConfigChange(
                 this, EVENT_BIND_DATA_SERVICE, null);
 
@@ -587,9 +571,10 @@
         // Read package name from resource overlay
         packageName = mPhone.getContext().getResources().getString(resourceId);
 
-        PersistableBundle b = mCarrierConfigManager.getConfigForSubId(mPhone.getSubId());
-
-        if (b != null && !TextUtils.isEmpty(b.getString(carrierConfig))) {
+        PersistableBundle b =
+                CarrierConfigManager.getCarrierConfigSubset(
+                        mPhone.getContext(), mPhone.getSubId(), carrierConfig);
+        if (!b.isEmpty() && !TextUtils.isEmpty(b.getString(carrierConfig))) {
             // If carrier config overrides it, use the one from carrier config
             packageName = b.getString(carrierConfig, packageName);
         }
@@ -636,9 +621,10 @@
         // Read package name from resource overlay
         className = mPhone.getContext().getResources().getString(resourceId);
 
-        PersistableBundle b = mCarrierConfigManager.getConfigForSubId(mPhone.getSubId());
-
-        if (b != null && !TextUtils.isEmpty(b.getString(carrierConfig))) {
+        PersistableBundle b =
+                CarrierConfigManager.getCarrierConfigSubset(
+                        mPhone.getContext(), mPhone.getSubId(), carrierConfig);
+        if (!b.isEmpty() && !TextUtils.isEmpty(b.getString(carrierConfig))) {
             // If carrier config overrides it, use the one from carrier config
             className = b.getString(carrierConfig, className);
         }
@@ -760,14 +746,19 @@
      * @param onCompleteMessage The result callback for this request.
      */
     public void startHandover(int cid, @NonNull Message onCompleteMessage) {
-        DataServiceCallbackWrapper callback =
-                setupCallbackHelper("startHandover", onCompleteMessage);
-        if (callback == null) {
-            loge("startHandover: callback == null");
+        if (DBG) log("startHandover");
+        if (!mBound) {
+            loge("Data service not bound.");
             sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
             return;
         }
 
+        DataServiceCallbackWrapper callback =
+                new DataServiceCallbackWrapper("startHandover");
+        if (onCompleteMessage != null) {
+            mMessageMap.put(callback.asBinder(), onCompleteMessage);
+        }
+
         try {
             sendMessageDelayed(obtainMessage(EVENT_WATCHDOG_TIMEOUT, callback),
                     REQUEST_UNRESPONDED_TIMEOUT);
@@ -790,13 +781,19 @@
      * @param onCompleteMessage The result callback for this request.
      */
     public void cancelHandover(int cid, @NonNull Message onCompleteMessage) {
-        DataServiceCallbackWrapper callback =
-                setupCallbackHelper("cancelHandover", onCompleteMessage);
-        if (callback == null) {
+        if (DBG) log("cancelHandover");
+        if (!mBound) {
+            loge("Data service not bound.");
             sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
             return;
         }
 
+        DataServiceCallbackWrapper callback =
+                new DataServiceCallbackWrapper("cancelHandover");
+        if (onCompleteMessage != null) {
+            mMessageMap.put(callback.asBinder(), onCompleteMessage);
+        }
+
         try {
             sendMessageDelayed(obtainMessage(EVENT_WATCHDOG_TIMEOUT, callback),
                     REQUEST_UNRESPONDED_TIMEOUT);
@@ -808,26 +805,6 @@
         }
     }
 
-    @Nullable
-    private DataServiceCallbackWrapper setupCallbackHelper(
-            @NonNull final String operationName, @NonNull final Message onCompleteMessage) {
-        if (DBG) log(operationName);
-        if (!mBound) {
-            sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
-            return null;
-        }
-
-        DataServiceCallbackWrapper callback =
-                new DataServiceCallbackWrapper(operationName);
-        if (onCompleteMessage != null) {
-            if (DBG) log(operationName + ": onCompleteMessage set");
-            mMessageMap.put(callback.asBinder(), onCompleteMessage);
-        } else {
-            if (DBG) log(operationName + ": onCompleteMessage not set");
-        }
-        return callback;
-    }
-
     /**
      * Set an APN to initial attach network.
      *
diff --git a/src/java/com/android/internal/telephony/data/DataSettingsManager.java b/src/java/com/android/internal/telephony/data/DataSettingsManager.java
index e2c1aff..5178ae4 100644
--- a/src/java/com/android/internal/telephony/data/DataSettingsManager.java
+++ b/src/java/com/android/internal/telephony/data/DataSettingsManager.java
@@ -19,7 +19,6 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.content.ContentResolver;
-import android.content.Context;
 import android.content.SharedPreferences;
 import android.os.Handler;
 import android.os.Looper;
@@ -29,12 +28,13 @@
 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;
 import android.telephony.TelephonyRegistryManager;
 import android.telephony.data.ApnSetting;
 import android.telephony.data.ApnSetting.ApnType;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
@@ -42,12 +42,19 @@
 
 import com.android.internal.telephony.GlobalSettingsHelper;
 import com.android.internal.telephony.Phone;
+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;
 import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.Executor;
@@ -58,8 +65,9 @@
  * data roaming settings, etc...
  */
 public class DataSettingsManager extends Handler {
-    /** Event for data config updated. */
-    private static final int EVENT_DATA_CONFIG_UPDATED = 1;
+    /** Invalid mobile data policy **/
+    private static final int INVALID_MOBILE_DATA_POLICY = -1;
+
     /** Event for call state changed. */
     private static final int EVENT_CALL_STATE_CHANGED = 2;
     /** Event for subscriptions updated. */
@@ -68,10 +76,9 @@
     private static final int EVENT_SET_DATA_ENABLED_FOR_REASON = 5;
     /** Event for set data roaming enabled. */
     private static final int EVENT_SET_DATA_ROAMING_ENABLED = 6;
-    /** Event for set always allow MMS data. */
-    private static final int EVENT_SET_ALWAYS_ALLOW_MMS_DATA = 7;
-    /** Event for set allow data during voice call. */
-    private static final int EVENT_SET_ALLOW_DATA_DURING_VOICE_CALL = 8;
+    /** Event for set mobile data policy. */
+    private static final int EVENT_SET_MOBILE_DATA_POLICY = 7;
+
     /** Event for device provisioned changed. */
     private static final int EVENT_PROVISIONED_CHANGED = 9;
     /** Event for provisioning data enabled setting changed. */
@@ -84,8 +91,8 @@
     private final SettingsObserver mSettingsObserver;
     private final String mLogTag;
     private final LocalLog mLocalLog = new LocalLog(128);
+    private Set<Integer> mEnabledMobileDataPolicy = new HashSet<>();
     private int mSubId;
-    private DataEnabledOverride mDataEnabledOverride;
 
     /** Data config manager */
     private final @NonNull DataConfigManager mDataConfigManager;
@@ -172,7 +179,7 @@
         mResolver = mPhone.getContext().getContentResolver();
         registerCallback(callback);
         mDataConfigManager = dataNetworkController.getDataConfigManager();
-        mDataEnabledOverride = getDataEnabledOverride();
+        refreshEnabledMobileDataPolicy();
         mSettingsObserver = new SettingsObserver(mPhone.getContext(), this);
         mDataEnabledSettings.put(TelephonyManager.DATA_ENABLED_REASON_POLICY, true);
         mDataEnabledSettings.put(TelephonyManager.DATA_ENABLED_REASON_CARRIER, true);
@@ -186,19 +193,13 @@
     @Override
     public void handleMessage(Message msg) {
         switch (msg.what) {
-            case EVENT_DATA_CONFIG_UPDATED: {
-                if (mDataConfigManager.isConfigCarrierSpecific()) {
-                    setDefaultDataRoamingEnabled();
-                }
-                break;
-            }
             case EVENT_CALL_STATE_CHANGED: {
                 updateDataEnabledAndNotify(TelephonyManager.DATA_ENABLED_REASON_OVERRIDE);
                 break;
             }
             case EVENT_SUBSCRIPTIONS_CHANGED: {
                 mSubId = (int) msg.obj;
-                mDataEnabledOverride = getDataEnabledOverride();
+                refreshEnabledMobileDataPolicy();
                 updateDataEnabledAndNotify(TelephonyManager.DATA_ENABLED_REASON_USER);
                 mPhone.notifyUserMobileDataStateChanged(isUserDataEnabled());
                 break;
@@ -232,34 +233,10 @@
                 setDataRoamingFromUserAction();
                 break;
             }
-            case EVENT_SET_ALWAYS_ALLOW_MMS_DATA: {
-                boolean alwaysAllow = (boolean) msg.obj;
-                if (alwaysAllow == isMmsAlwaysAllowed()) {
-                    break;
-                }
-                logl("AlwaysAllowMmsData changed to " + alwaysAllow);
-                mDataEnabledOverride.setAlwaysAllowMms(alwaysAllow);
-                if (SubscriptionController.getInstance()
-                        .setDataEnabledOverrideRules(mSubId, mDataEnabledOverride.getRules())) {
-                    updateDataEnabledAndNotify(TelephonyManager.DATA_ENABLED_REASON_OVERRIDE);
-                    notifyDataEnabledOverrideChanged(alwaysAllow,
-                            TelephonyManager.MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED);
-                }
-                break;
-            }
-            case EVENT_SET_ALLOW_DATA_DURING_VOICE_CALL: {
-                boolean allow = (boolean) msg.obj;
-                if (allow == isDataAllowedInVoiceCall()) {
-                    break;
-                }
-                logl("AllowDataDuringVoiceCall changed to " + allow);
-                mDataEnabledOverride.setDataAllowedInVoiceCall(allow);
-                if (SubscriptionController.getInstance()
-                        .setDataEnabledOverrideRules(mSubId, mDataEnabledOverride.getRules())) {
-                    updateDataEnabledAndNotify(TelephonyManager.DATA_ENABLED_REASON_OVERRIDE);
-                    notifyDataEnabledOverrideChanged(allow, TelephonyManager
-                            .MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL);
-                }
+            case EVENT_SET_MOBILE_DATA_POLICY: {
+                int mobileDataPolicy = msg.arg1;
+                boolean enable = msg.arg2 == 1;
+                onSetMobileDataPolicy(mobileDataPolicy, enable);
                 break;
             }
             case EVENT_PROVISIONED_CHANGED:
@@ -280,7 +257,14 @@
      * Called when needed to register for all events that data network controller is interested.
      */
     private void onInitialize() {
-        mDataConfigManager.registerForConfigUpdate(this, EVENT_DATA_CONFIG_UPDATED);
+        mDataConfigManager.registerCallback(new DataConfigManagerCallback(this::post) {
+            @Override
+            public void onCarrierConfigChanged() {
+                if (mDataConfigManager.isConfigCarrierSpecific()) {
+                    setDefaultDataRoamingEnabled();
+                }
+            }
+        });
         mSettingsObserver.observe(Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
                 EVENT_PROVISIONED_CHANGED);
         mSettingsObserver.observe(
@@ -399,9 +383,9 @@
             return isProvisioningDataEnabled();
         } else {
             boolean userDataEnabled = isUserDataEnabled();
-            // Check if we should temporarily enable data in certain conditions.
-            boolean isDataEnabledOverridden = mDataEnabledOverride
-                    .shouldOverrideDataEnabledSettings(mPhone, apnType);
+            // Check if we should temporarily enable data based on mobile data policy.
+            boolean isDataEnabledOverridden = isDataEnabledOverriddenForApn(apnType);
+
 
             return ((userDataEnabled || isDataEnabledOverridden)
                     && mDataEnabledSettings.get(TelephonyManager.DATA_ENABLED_REASON_POLICY)
@@ -410,10 +394,11 @@
         }
     }
 
-    private static boolean isStandAloneOpportunistic(int subId, Context context) {
-        SubscriptionInfo info = SubscriptionController.getInstance().getActiveSubscriptionInfo(
-                subId, context.getOpPackageName(), context.getAttributionTag());
-        return (info != null) && info.isOpportunistic() && info.getGroupUuid() == null;
+    private boolean isStandAloneOpportunistic(int subId) {
+        SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
+                .getSubscriptionInfoInternal(subId);
+        return subInfo != null && subInfo.isOpportunistic()
+                && TextUtils.isEmpty(subInfo.getGroupUuid());
     }
 
     /**
@@ -423,7 +408,7 @@
      */
     private void setUserDataEnabled(boolean enabled, String callingPackage) {
         // Can't disable data for stand alone opportunistic subscription.
-        if (isStandAloneOpportunistic(mSubId, mPhone.getContext()) && !enabled) return;
+        if (isStandAloneOpportunistic(mSubId) && !enabled) return;
         boolean changed = GlobalSettingsHelper.setInt(mPhone.getContext(),
                 Settings.Global.MOBILE_DATA, mSubId, (enabled ? 1 : 0));
         log("Set user data enabled to " + enabled + ", changed=" + changed + ", callingPackage="
@@ -445,7 +430,7 @@
         }
 
         // User data should always be true for opportunistic subscription.
-        if (isStandAloneOpportunistic(mSubId, mPhone.getContext())) return true;
+        if (isStandAloneOpportunistic(mSubId)) return true;
 
         boolean defaultVal = TelephonyProperties.mobile_data().orElse(true);
 
@@ -544,42 +529,37 @@
      * has not manually set the value. The default value is {@link #isDefaultDataRoamingEnabled()}.
      */
     public void setDefaultDataRoamingEnabled() {
-        // For SSSS, this is a per-phone property from DATA_ROAMING_IS_USER_SETTING_KEY.
-        // For DSDS, this is a per-sub property from Settings.Global.DATA_ROAMING + subId.
         // If the user has not manually set the value, use the default value.
-        boolean useCarrierSpecificDefault = false;
-        if (mPhone.getContext().getSystemService(TelephonyManager.class).getSimCount() != 1) {
-            String setting = Settings.Global.DATA_ROAMING + mPhone.getSubId();
-            try {
-                Settings.Global.getInt(mResolver, setting);
-            } catch (Settings.SettingNotFoundException ex) {
-                // For multi-SIM phones, use the default value if uninitialized.
-                useCarrierSpecificDefault = true;
-            }
-        } else if (!isDataRoamingFromUserAction()) {
-            // For single-SIM phones, use the default value if user action is not set.
-            useCarrierSpecificDefault = true;
-        }
-        log("setDefaultDataRoamingEnabled: useCarrierSpecificDefault=" + useCarrierSpecificDefault);
-        if (useCarrierSpecificDefault) {
-            boolean defaultVal = isDefaultDataRoamingEnabled();
-            setDataRoamingEnabledInternal(defaultVal);
+        if (!isDataRoamingFromUserAction()) {
+            setDataRoamingEnabledInternal(isDefaultDataRoamingEnabled());
         }
     }
 
     /**
-     * Get whether the user has manually enabled or disabled data roaming from settings.
-     * @return {@code true} if the user has enabled data roaming and {@code false} if they have not.
+     * Get whether the user has manually enabled or disabled data roaming from settings for the
+     * current subscription.
+     * @return {@code true} if the user has manually enabled data roaming for the current
+     *         subscription and {@code false} if they have not.
      */
     private boolean isDataRoamingFromUserAction() {
-        final SharedPreferences sp = PreferenceManager
-                .getDefaultSharedPreferences(mPhone.getContext());
-        // Since we don't want to unset user preferences after a system update, default to true if
-        // the preference does not exist and set it to false explicitly from factory reset.
-        if (!sp.contains(Phone.DATA_ROAMING_IS_USER_SETTING_KEY)) {
-            sp.edit().putBoolean(Phone.DATA_ROAMING_IS_USER_SETTING_KEY, false).commit();
+        String key = Phone.DATA_ROAMING_IS_USER_SETTING_KEY + mPhone.getSubId();
+        final SharedPreferences sp =
+                PreferenceManager.getDefaultSharedPreferences(mPhone.getContext());
+
+        // Set the default roaming from user action value if the preference doesn't exist
+        if (!sp.contains(key)) {
+            if (sp.contains(Phone.DATA_ROAMING_IS_USER_SETTING_KEY)) {
+                log("Reusing previous roaming from user action value for backwards compatibility.");
+                sp.edit().putBoolean(key, true).commit();
+            } else {
+                log("Clearing roaming from user action value for new or upgrading devices.");
+                sp.edit().putBoolean(key, false).commit();
+            }
         }
-        return sp.getBoolean(Phone.DATA_ROAMING_IS_USER_SETTING_KEY, true);
+
+        boolean isUserSetting = sp.getBoolean(key, true);
+        log("isDataRoamingFromUserAction: key=" + key + ", isUserSetting=" + isUserSetting);
+        return isUserSetting;
     }
 
     /**
@@ -588,49 +568,76 @@
      * {@link #isDefaultDataRoamingEnabled()} will continue to be used.
      */
     private void setDataRoamingFromUserAction() {
-        final SharedPreferences.Editor sp = PreferenceManager
-                .getDefaultSharedPreferences(mPhone.getContext()).edit();
-        sp.putBoolean(Phone.DATA_ROAMING_IS_USER_SETTING_KEY, true).commit();
+        String key = Phone.DATA_ROAMING_IS_USER_SETTING_KEY + mPhone.getSubId();
+        log("setDataRoamingFromUserAction: key=" + key);
+        final SharedPreferences.Editor sp =
+                PreferenceManager.getDefaultSharedPreferences(mPhone.getContext()).edit();
+        sp.putBoolean(key, true).commit();
     }
 
-    private @NonNull DataEnabledOverride getDataEnabledOverride() {
-        return new DataEnabledOverride(SubscriptionController.getInstance()
-                .getDataEnabledOverrideRules(mSubId));
+    /** Refresh the enabled mobile data policies from Telephony database */
+    private void refreshEnabledMobileDataPolicy() {
+        SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
+                .getSubscriptionInfoInternal(mSubId);
+        if (subInfo != null) {
+            mEnabledMobileDataPolicy = getMobileDataPolicyEnabled(
+                    subInfo.getEnabledMobileDataPolicies());
+        }
     }
 
     /**
-     * Set whether to always allow the MMS data connection.
-     * @param alwaysAllow {@code true} if MMS data is always allowed and {@code false} otherwise.
+     * @return {@code true} If the mobile data policy is enabled
      */
-    public void setAlwaysAllowMmsData(boolean alwaysAllow) {
-        obtainMessage(EVENT_SET_ALWAYS_ALLOW_MMS_DATA, alwaysAllow).sendToTarget();
+    public boolean isMobileDataPolicyEnabled(@MobileDataPolicy int mobileDataPolicy) {
+        return mEnabledMobileDataPolicy.contains(mobileDataPolicy);
     }
 
     /**
-     * Check whether MMS is always allowed.
-     * @return {@code true} if MMS is always allowed and {@code false} otherwise.
+     * Set mobile data policy enabled status
+     * @param mobileDataPolicy The mobile data policy to set
+     * @param enable {@code true} to enable the policy; {@code false} to disable.
      */
-    public boolean isMmsAlwaysAllowed() {
-        return mDataEnabledOverride.isMmsAlwaysAllowed();
+    public void setMobileDataPolicy(@MobileDataPolicy int mobileDataPolicy, boolean enable) {
+        obtainMessage(EVENT_SET_MOBILE_DATA_POLICY, mobileDataPolicy, enable ? 1 : 0)
+                .sendToTarget();
     }
 
     /**
-     * Set whether to allow mobile data during voice call. This is used for allowing data on the
-     * non-default data SIM. When a voice call is placed on the non-default data SIM on DSDS
-     * devices, users will not be able to use mobile data. By calling this API, data will be
-     * temporarily enabled on the non-default data SIM during the life cycle of the voice call.
-     * @param allow {@code true} if data is allowed during a voice call and {@code false} otherwise.
+     * Store data mobile policy to Telephony database.
+     *
+     * @param mobileDataPolicy The mobile data policy that overrides user data enabled setting.
+     * @param enable {@code true} to enable the policy; {@code false} to remove the policy.
      */
-    public void setAllowDataDuringVoiceCall(boolean allow) {
-        obtainMessage(EVENT_SET_ALLOW_DATA_DURING_VOICE_CALL, allow).sendToTarget();
+    private void onSetMobileDataPolicy(@MobileDataPolicy int mobileDataPolicy, boolean enable) {
+        if (enable == isMobileDataPolicyEnabled(mobileDataPolicy)) {
+            return;
+        }
+        metricsRecordSetMobileDataPolicy(mobileDataPolicy);
+
+        if (enable) {
+            mEnabledMobileDataPolicy.add(mobileDataPolicy);
+        } else {
+            mEnabledMobileDataPolicy.remove(mobileDataPolicy);
+        }
+
+        String enabledMobileDataPolicies = mEnabledMobileDataPolicy.stream().map(String::valueOf)
+                .collect(Collectors.joining(","));
+        SubscriptionManagerService.getInstance().setEnabledMobileDataPolicies(mSubId,
+                enabledMobileDataPolicies);
+        logl(TelephonyUtils.mobileDataPolicyToString(mobileDataPolicy) + " changed to "
+                + enable);
+        updateDataEnabledAndNotify(TelephonyManager.DATA_ENABLED_REASON_OVERRIDE);
+        notifyDataEnabledOverrideChanged(enable, mobileDataPolicy);
     }
 
     /**
-     * Check whether data is allowed during a voice call.
-     * @return {@code true} if data is allowed during voice call and {@code false} otherwise.
+     * Record the number of times a mobile data policy is toggled to metrics.
+     * @param mobileDataPolicy The mobile data policy that's toggled
      */
-    public boolean isDataAllowedInVoiceCall() {
-        return mDataEnabledOverride.isDataAllowedInVoiceCall();
+    private void metricsRecordSetMobileDataPolicy(@MobileDataPolicy int mobileDataPolicy) {
+        if (mobileDataPolicy == TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH) {
+            DeviceTelephonyPropertiesStats.recordAutoDataSwitchFeatureToggle();
+        }
     }
 
     /**
@@ -659,6 +666,85 @@
     }
 
     /**
+     * Return the parsed mobile data policies.
+     *
+     * @param policies New mobile data policies in String format.
+     * @return A Set of parsed mobile data policies.
+     */
+    public @NonNull @MobileDataPolicy Set<Integer> getMobileDataPolicyEnabled(
+            @NonNull String policies) {
+        Set<Integer> mobileDataPolicies = new HashSet<>();
+        String[] rulesString = policies.trim().split("\\s*,\\s*");
+        for (String rule : rulesString) {
+            if (!TextUtils.isEmpty(rule)) {
+                int parsedDataPolicy = parsePolicyFrom(rule);
+                if (parsedDataPolicy != INVALID_MOBILE_DATA_POLICY) {
+                    mobileDataPolicies.add(parsedDataPolicy);
+                }
+            }
+        }
+        return mobileDataPolicies;
+    }
+
+    /**
+     * Parse a mobile data policy retrieved from Telephony db.
+     * If the policy is in legacy format, convert it into the corresponding mobile data policy.
+     *
+     * @param policy Mobile data policy to be parsed from.
+     * @return Parsed mobile data policy. {@link #INVALID_MOBILE_DATA_POLICY} if string can't be
+     * parsed into a mobile data policy.
+     */
+    private @MobileDataPolicy int parsePolicyFrom(@NonNull String policy) {
+        int dataPolicy;
+        try {
+            // parse as new override policy
+            dataPolicy = Integer.parseInt(policy);
+        } catch (NumberFormatException e) {
+            dataPolicy = INVALID_MOBILE_DATA_POLICY;
+            loge("parsePolicyFrom: invalid mobile data policy format: "  + policy);
+        }
+        return dataPolicy;
+    }
+
+    /**
+     * Check if data enabled is temporarily overridden in certain conditions.
+     *
+     * @param apnType The APN type to check.
+     * @return {@code true} if data enabled should be overridden.
+     */
+    private boolean isDataEnabledOverriddenForApn(@ApnType int apnType) {
+        boolean overridden = false;
+
+        // mobile data policy : MMS always allowed
+        if (isMobileDataPolicyEnabled(TelephonyManager.MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED)) {
+            overridden = apnType == ApnSetting.TYPE_MMS;
+        }
+
+        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 = overridden || isNonDds && mPhone.getState() != PhoneConstants.State.IDLE;
+        }
+
+        // mobile data policy : auto data switch
+        if (isMobileDataPolicyEnabled(TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH)) {
+            // check user enabled data on the default data phone
+            Phone defaultDataPhone = PhoneFactory.getPhone(SubscriptionManagerService.getInstance()
+                    .getPhoneId(SubscriptionManagerService.getInstance()
+                            .getDefaultDataSubId()));
+            if (defaultDataPhone == null) {
+                loge("isDataEnabledOverriddenForApn: unexpected defaultDataPhone is null");
+            } else {
+                overridden = overridden || isNonDds && defaultDataPhone.isUserDataEnabled();
+            }
+        }
+        return overridden;
+    }
+
+    /**
      * Register the callback for receiving information from {@link DataSettingsManager}.
      *
      * @param callback The callback.
@@ -700,7 +786,8 @@
                 + ", isProvisioningDataEnabled=" + isProvisioningDataEnabled()
                 + ", mIsDataEnabled=" + mIsDataEnabled
                 + ", mDataEnabledSettings=" + mDataEnabledSettings
-                + ", mDataEnabledOverride=" + mDataEnabledOverride
+                + ", mEnabledMobileDataPolicy=" + mEnabledMobileDataPolicy.stream()
+                .map(TelephonyUtils::mobileDataPolicyToString).collect(Collectors.joining(","))
                 + "]";
     }
 
@@ -756,7 +843,8 @@
                 .map(entry ->
                         dataEnabledChangedReasonToString(entry.getKey()) + "=" + entry.getValue())
                 .collect(Collectors.joining(", ")));
-        pw.println("mDataEnabledOverride=" + mDataEnabledOverride);
+        pw.println("mEnabledMobileDataPolicy=" + mEnabledMobileDataPolicy.stream()
+                .map(TelephonyUtils::mobileDataPolicyToString).collect(Collectors.joining(",")));
         pw.println("Local logs:");
         pw.increaseIndent();
         mLocalLog.dump(fd, pw, args);
diff --git a/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java b/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java
index 22d0498..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;
@@ -26,18 +28,19 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.SystemClock;
+import android.provider.Settings;
 import android.telephony.Annotation.RadioPowerState;
 import android.telephony.Annotation.ValidationStatus;
 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;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.data.DataConfigManager.DataConfigManagerCallback;
 import com.android.internal.telephony.data.DataNetworkController.DataNetworkControllerCallback;
 import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback;
 import com.android.internal.telephony.metrics.DataStallRecoveryStats;
@@ -124,9 +127,6 @@
     /** The data stall recovered by user (Mobile Data Power off/on). */
     private static final int RECOVERED_REASON_USER = 3;
 
-    /** Event for data config updated. */
-    private static final int EVENT_DATA_CONFIG_UPDATED = 1;
-
     /** Event for triggering recovery action. */
     private static final int EVENT_DO_RECOVERY = 2;
 
@@ -147,17 +147,20 @@
     private final @NonNull DataServiceManager mWwanDataServiceManager;
 
     /** The data stall recovery action. */
-    private @RecoveryAction int mRecovryAction;
+    private @RecoveryAction int mRecoveryAction;
     /** The elapsed real time of last recovery attempted */
     private @ElapsedRealtimeLong long mTimeLastRecoveryStartMs;
     /** Whether current network is good or not */
     private boolean mIsValidNetwork;
+    /** Whether data stall recovery is triggered or not */
+    private boolean mRecoveryTriggered = false;
     /** Whether data stall happened or not. */
     private boolean mDataStalled;
     /** Whether the result of last action(RADIO_RESTART) reported. */
     private boolean mLastActionReported;
     /** The real time for data stall start. */
-    private @ElapsedRealtimeLong long mDataStallStartMs;
+    @VisibleForTesting
+    public @ElapsedRealtimeLong long mDataStallStartMs;
     /** Last data stall recovery action. */
     private @RecoveryAction int mLastAction;
     /** Last radio power state. */
@@ -166,12 +169,16 @@
     private boolean mNetworkCheckTimerStarted = false;
     /** Whether radio state changed during data stall. */
     private boolean mRadioStateChangedDuringDataStall;
+    /** Whether airplane mode enabled during data stall. */
+    private boolean mIsAirPlaneModeEnableDuringDataStall;
     /** Whether mobile data change to Enabled during data stall. */
     private boolean mMobileDataChangedToEnabledDuringDataStall;
     /** Whether attempted all recovery steps. */
     private boolean mIsAttemptedAllSteps;
     /** Whether internet network connected. */
     private boolean mIsInternetNetworkConnected;
+    /** The durations for current recovery action */
+    private @ElapsedRealtimeLong long mTimeElapsedOfCurrentAction;
 
     /** The array for the timers between recovery actions. */
     private @NonNull long[] mDataStallRecoveryDelayMillisArray;
@@ -245,7 +252,12 @@
 
     /** Register for all events that data stall monitor is interested. */
     private void registerAllEvents() {
-        mDataConfigManager.registerForConfigUpdate(this, EVENT_DATA_CONFIG_UPDATED);
+        mDataConfigManager.registerCallback(new DataConfigManagerCallback(this::post) {
+            @Override
+            public void onCarrierConfigChanged() {
+                DataStallRecoveryManager.this.onCarrierConfigUpdated();
+            }
+        });
         mDataNetworkController.registerDataNetworkControllerCallback(
                 new DataNetworkControllerCallback(this::post) {
                     @Override
@@ -256,7 +268,7 @@
 
                     @Override
                     public void onInternetDataNetworkConnected(
-                            @NonNull List<DataProfile> dataProfiles) {
+                            @NonNull List<DataNetwork> internetNetworks) {
                         mIsInternetNetworkConnected = true;
                         logl("onInternetDataNetworkConnected");
                     }
@@ -274,9 +286,6 @@
     public void handleMessage(Message msg) {
         logv("handleMessage = " + msg);
         switch (msg.what) {
-            case EVENT_DATA_CONFIG_UPDATED:
-                onDataConfigUpdated();
-                break;
             case EVENT_DO_RECOVERY:
                 doRecovery();
                 break;
@@ -285,6 +294,12 @@
                 if (mDataStalled) {
                     // Store the radio state changed flag only when data stall occurred.
                     mRadioStateChangedDuringDataStall = true;
+                    if (Settings.Global.getInt(
+                                    mPhone.getContext().getContentResolver(),
+                                    Settings.Global.AIRPLANE_MODE_ON,
+                                    0) != 0) {
+                        mIsAirPlaneModeEnableDuringDataStall = true;
+                    }
                 }
                 break;
             default:
@@ -319,8 +334,8 @@
         return mSkipRecoveryActionArray[recoveryAction];
     }
 
-    /** Called when data config was updated. */
-    private void onDataConfigUpdated() {
+    /** Called when carrier config was updated. */
+    private void onCarrierConfigUpdated() {
         updateDataStallRecoveryConfigs();
     }
 
@@ -343,19 +358,21 @@
      */
     private void reset() {
         mIsValidNetwork = true;
+        mRecoveryTriggered = false;
         mIsAttemptedAllSteps = false;
         mRadioStateChangedDuringDataStall = false;
+        mIsAirPlaneModeEnableDuringDataStall = false;
         mMobileDataChangedToEnabledDuringDataStall = false;
         cancelNetworkCheckTimer();
         mTimeLastRecoveryStartMs = 0;
         mLastAction = RECOVERY_ACTION_GET_DATA_CALL_LIST;
-        mRecovryAction = RECOVERY_ACTION_GET_DATA_CALL_LIST;
+        mRecoveryAction = RECOVERY_ACTION_GET_DATA_CALL_LIST;
     }
 
     /**
      * Called when internet validation status changed.
      *
-     * @param validationStatus Validation status.
+     * @param status Validation status.
      */
     private void onInternetValidationStatusChanged(@ValidationStatus int status) {
         logl("onInternetValidationStatusChanged: " + DataUtils.validationStatusToString(status));
@@ -363,15 +380,12 @@
         setNetworkValidationState(isValid);
         if (isValid) {
             reset();
-        } else {
-            if (mIsValidNetwork || isRecoveryAlreadyStarted()) {
-                mIsValidNetwork = false;
-                if (isRecoveryNeeded(true)) {
-                    log("trigger data stall recovery");
-                    mTimeLastRecoveryStartMs = SystemClock.elapsedRealtime();
-                    sendMessage(obtainMessage(EVENT_DO_RECOVERY));
-                }
-            }
+        } else if (isRecoveryNeeded(true)) {
+            // Set the network as invalid, because recovery is needed
+            mIsValidNetwork = false;
+            log("trigger data stall recovery");
+            mTimeLastRecoveryStartMs = SystemClock.elapsedRealtime();
+            sendMessage(obtainMessage(EVENT_DO_RECOVERY));
         }
     }
 
@@ -380,6 +394,7 @@
         mTimeLastRecoveryStartMs = 0;
         mMobileDataChangedToEnabledDuringDataStall = false;
         mRadioStateChangedDuringDataStall = false;
+        mIsAirPlaneModeEnableDuringDataStall = false;
         setRecoveryAction(RECOVERY_ACTION_GET_DATA_CALL_LIST);
     }
 
@@ -391,8 +406,8 @@
     @VisibleForTesting
     @RecoveryAction
     public int getRecoveryAction() {
-        log("getRecoveryAction: " + recoveryActionToString(mRecovryAction));
-        return mRecovryAction;
+        log("getRecoveryAction: " + recoveryActionToString(mRecoveryAction));
+        return mRecoveryAction;
     }
 
     /**
@@ -402,24 +417,24 @@
      */
     @VisibleForTesting
     public void setRecoveryAction(@RecoveryAction int action) {
-        mRecovryAction = action;
+        mRecoveryAction = action;
 
         // Check if the mobile data enabled is TRUE, it means that the mobile data setting changed
         // from DISABLED to ENABLED, we will set the next recovery action to
         // RECOVERY_ACTION_RADIO_RESTART due to already did the RECOVERY_ACTION_CLEANUP.
         if (mMobileDataChangedToEnabledDuringDataStall
-                && mRecovryAction < RECOVERY_ACTION_RADIO_RESTART) {
-            mRecovryAction = RECOVERY_ACTION_RADIO_RESTART;
+                && mRecoveryAction < RECOVERY_ACTION_RADIO_RESTART) {
+            mRecoveryAction = RECOVERY_ACTION_RADIO_RESTART;
         }
         // Check if the radio state changed from off to on, it means that the modem already
         // did the radio restart, we will set the next action to RECOVERY_ACTION_RESET_MODEM.
         if (mRadioStateChangedDuringDataStall
                 && mRadioPowerState == TelephonyManager.RADIO_POWER_ON) {
-            mRecovryAction = RECOVERY_ACTION_RESET_MODEM;
+            mRecoveryAction = RECOVERY_ACTION_RESET_MODEM;
         }
         // To check the flag from DataConfigManager if we need to skip the step.
-        if (shouldSkipRecoveryAction(mRecovryAction)) {
-            switch (mRecovryAction) {
+        if (shouldSkipRecoveryAction(mRecoveryAction)) {
+            switch (mRecoveryAction) {
                 case RECOVERY_ACTION_GET_DATA_CALL_LIST:
                     setRecoveryAction(RECOVERY_ACTION_CLEANUP);
                     break;
@@ -435,7 +450,7 @@
             }
         }
 
-        log("setRecoveryAction: " + recoveryActionToString(mRecovryAction));
+        log("setRecoveryAction: " + recoveryActionToString(mRecoveryAction));
     }
 
     /**
@@ -444,7 +459,7 @@
      * @return {@code true} if recovery already started, {@code false} recovery not started.
      */
     private boolean isRecoveryAlreadyStarted() {
-        return getRecoveryAction() != RECOVERY_ACTION_GET_DATA_CALL_LIST;
+        return getRecoveryAction() != RECOVERY_ACTION_GET_DATA_CALL_LIST || mRecoveryTriggered;
     }
 
     /**
@@ -457,6 +472,15 @@
     }
 
     /**
+     * Get duration time for current recovery action.
+     *
+     * @return the time duration for current recovery action.
+     */
+    private long getDurationOfCurrentRecoveryMs() {
+        return (SystemClock.elapsedRealtime() - mTimeElapsedOfCurrentAction);
+    }
+
+    /**
      * Broadcast intent when data stall occurred.
      *
      * @param recoveryAction Send the data stall detected intent with RecoveryAction info.
@@ -466,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 */
@@ -531,6 +555,12 @@
     private boolean isRecoveryNeeded(boolean isNeedToCheckTimer) {
         logv("enter: isRecoveryNeeded()");
 
+        // Skip if network is invalid and recovery was not started yet
+        if (!mIsValidNetwork && !isRecoveryAlreadyStarted()) {
+            logl("skip when network still remains invalid and recovery was not started yet");
+            return false;
+        }
+
         // Skip recovery if we have already attempted all steps.
         if (mIsAttemptedAllSteps) {
             logl("skip retrying continue recovery action");
@@ -566,7 +596,6 @@
             logl("skip data stall recovery as data not connected");
             return false;
         }
-
         return true;
     }
 
@@ -576,39 +605,65 @@
      * @param isValid true for validation passed & false for validation failed
      */
     private void setNetworkValidationState(boolean isValid) {
+        boolean isLogNeeded = false;
+        int timeDuration = 0;
+        int timeDurationOfCurrentAction = 0;
+        boolean isFirstDataStall = false;
+        boolean isFirstValidationAfterDoRecovery = false;
+        @RecoveredReason int reason = getRecoveredReason(isValid);
         // Validation status is true and was not data stall.
         if (isValid && !mDataStalled) {
             return;
         }
 
         if (!mDataStalled) {
+            // First data stall
+            isLogNeeded = true;
             mDataStalled = true;
+            isFirstDataStall = true;
             mDataStallStartMs = SystemClock.elapsedRealtime();
             logl("data stall: start time = " + DataUtils.elapsedTimeToString(mDataStallStartMs));
-            return;
-        }
-
-        if (!mLastActionReported) {
-            @RecoveredReason int reason = getRecoveredReason(isValid);
-            int timeDuration = (int) (SystemClock.elapsedRealtime() - mDataStallStartMs);
-            logl(
-                    "data stall: lastaction = "
-                            + recoveryActionToString(mLastAction)
-                            + ", isRecovered = "
-                            + isValid
-                            + ", reason = "
-                            + recoveredReasonToString(reason)
-                            + ", TimeDuration = "
-                            + timeDuration);
-            DataStallRecoveryStats.onDataStallEvent(
-                    mLastAction, mPhone, isValid, timeDuration, reason);
+        } else if (!mLastActionReported) {
+            // When the first validation status appears, enter this block.
+            isLogNeeded = true;
+            timeDuration = (int) (SystemClock.elapsedRealtime() - mDataStallStartMs);
             mLastActionReported = true;
+            isFirstValidationAfterDoRecovery = true;
         }
 
         if (isValid) {
+            // When the validation passed(mobile data resume), enter this block.
+            isLogNeeded = true;
+            timeDuration = (int) (SystemClock.elapsedRealtime() - mDataStallStartMs);
             mLastActionReported = false;
             mDataStalled = false;
         }
+
+        if (isLogNeeded) {
+            timeDurationOfCurrentAction =
+                ((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);
+            logl(
+                    "data stall: "
+                    + (isFirstDataStall == true ? "start" : isValid == false ? "in process" : "end")
+                    + ", lastaction="
+                    + recoveryActionToString(mLastAction)
+                    + ", isRecovered="
+                    + isValid
+                    + ", reason="
+                    + recoveredReasonToString(reason)
+                    + ", isFirstValidationAfterDoRecovery="
+                    + isFirstValidationAfterDoRecovery
+                    + ", TimeDuration="
+                    + timeDuration
+                    + ", TimeDurationForCurrentRecoveryAction="
+                    + timeDurationOfCurrentAction);
+        }
     }
 
     /**
@@ -628,6 +683,9 @@
             if (mLastAction > RECOVERY_ACTION_CLEANUP) {
                 ret = RECOVERED_REASON_DSRM;
             }
+            if (mIsAirPlaneModeEnableDuringDataStall) {
+                ret = RECOVERED_REASON_USER;
+            }
         } else if (mMobileDataChangedToEnabledDuringDataStall) {
             ret = RECOVERED_REASON_USER;
         }
@@ -638,6 +696,7 @@
     private void doRecovery() {
         @RecoveryAction final int recoveryAction = getRecoveryAction();
         final int signalStrength = mPhone.getSignalStrength().getLevel();
+        mRecoveryTriggered = true;
 
         // DSRM used sendMessageDelayed to process the next event EVENT_DO_RECOVERY, so it need
         // to check the condition if DSRM need to process the recovery action.
@@ -654,6 +713,7 @@
         mLastActionReported = false;
         broadcastDataStallDetected(recoveryAction);
         mNetworkCheckTimerStarted = false;
+        mTimeElapsedOfCurrentAction = SystemClock.elapsedRealtime();
 
         switch (recoveryAction) {
             case RECOVERY_ACTION_GET_DATA_CALL_LIST:
@@ -799,6 +859,7 @@
 
         pw.println("mIsValidNetwork=" + mIsValidNetwork);
         pw.println("mIsInternetNetworkConnected=" + mIsInternetNetworkConnected);
+        pw.println("mIsAirPlaneModeEnableDuringDataStall=" + mIsAirPlaneModeEnableDuringDataStall);
         pw.println("mDataStalled=" + mDataStalled);
         pw.println("mLastAction=" + recoveryActionToString(mLastAction));
         pw.println("mIsAttemptedAllSteps=" + mIsAttemptedAllSteps);
diff --git a/src/java/com/android/internal/telephony/data/DataUtils.java b/src/java/com/android/internal/telephony/data/DataUtils.java
index 0bfddc2..fc0c4f1 100644
--- a/src/java/com/android/internal/telephony/data/DataUtils.java
+++ b/src/java/com/android/internal/telephony/data/DataUtils.java
@@ -41,6 +41,7 @@
 import android.util.ArrayMap;
 
 import com.android.internal.telephony.data.DataNetworkController.NetworkRequestList;
+import com.android.telephony.Rlog;
 
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -61,6 +62,7 @@
     /** The time format for converting time to readable string. */
     private static final SimpleDateFormat TIME_FORMAT =
             new SimpleDateFormat("HH:mm:ss.SSS", Locale.US);
+    private static final String TAG = "DataUtils";
 
     /**
      * Get the network capability from the string.
@@ -164,6 +166,7 @@
             case NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH:
                 return "PRIORITIZE_BANDWIDTH";
             default:
+                loge("Unknown network capability(" + netCap + ")");
                 return "Unknown(" + netCap + ")";
         }
     }
@@ -209,7 +212,9 @@
         switch (status) {
             case NetworkAgent.VALIDATION_STATUS_VALID: return "VALID";
             case NetworkAgent.VALIDATION_STATUS_NOT_VALID: return "INVALID";
-            default: return "UNKNOWN(" + status + ")";
+            default:
+                loge("Unknown validation status(" + status + ")");
+                return "UNKNOWN(" + status + ")";
         }
     }
 
@@ -366,6 +371,7 @@
             case ImsFeature.FEATURE_MMTEL: return "MMTEL";
             case ImsFeature.FEATURE_RCS: return "RCS";
             default:
+                loge("Unknown IMS feature(" + imsFeature + ")");
                 return "Unknown(" + imsFeature + ")";
         }
     }
@@ -404,9 +410,27 @@
                             .boxed().collect(Collectors.toSet()),
                     v -> new NetworkRequestList()).add(networkRequest);
         }
-        // Sort the list, so the network request list contains higher priority will be in the front
-        // of the list.
-        return new ArrayList<>(requestsMap.values()).stream()
+        List<NetworkRequestList> requests = new ArrayList<>();
+        // Create separate groups for enterprise requests with different enterprise IDs.
+        for (NetworkRequestList requestList : requestsMap.values()) {
+            List<TelephonyNetworkRequest> enterpriseRequests = requestList.stream()
+                    .filter(request ->
+                            request.hasCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE))
+                    .collect(Collectors.toList());
+            if (enterpriseRequests.isEmpty()) {
+                requests.add(requestList);
+                continue;
+            }
+            // Key is the enterprise ID
+            Map<Integer, NetworkRequestList> enterpriseRequestsMap = new ArrayMap<>();
+            for (TelephonyNetworkRequest request : enterpriseRequests) {
+                enterpriseRequestsMap.computeIfAbsent(request.getCapabilityDifferentiator(),
+                        v -> new NetworkRequestList()).add(request);
+            }
+            requests.addAll(enterpriseRequestsMap.values());
+        }
+        // Sort the requests so the network request list with higher priority will be at the front.
+        return requests.stream()
                 .sorted((list1, list2) -> Integer.compare(
                         list2.get(0).getPriority(), list1.get(0).getPriority()))
                 .collect(Collectors.toList());
@@ -450,7 +474,9 @@
             case DataCallResponse.LINK_STATUS_INACTIVE: return "INACTIVE";
             case DataCallResponse.LINK_STATUS_ACTIVE: return "ACTIVE";
             case DataCallResponse.LINK_STATUS_DORMANT: return "DORMANT";
-            default: return "UNKNOWN(" + linkStatus + ")";
+            default:
+                loge("Unknown link status(" + linkStatus + ")");
+                return "UNKNOWN(" + linkStatus + ")";
         }
     }
 
@@ -487,7 +513,13 @@
             case TelephonyManager.DATA_ACTIVITY_OUT: return "OUT";
             case TelephonyManager.DATA_ACTIVITY_INOUT: return "INOUT";
             case TelephonyManager.DATA_ACTIVITY_DORMANT: return "DORMANT";
-            default: return "UNKNOWN(" + dataActivity + ")";
+            default:
+                loge("Unknown data activity(" + dataActivity + ")");
+                return "UNKNOWN(" + dataActivity + ")";
         }
     }
+
+    private static void loge(String msg) {
+        Rlog.e(TAG, msg);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/data/LinkBandwidthEstimator.java b/src/java/com/android/internal/telephony/data/LinkBandwidthEstimator.java
index 919bbc3..5ed12aa 100644
--- a/src/java/com/android/internal/telephony/data/LinkBandwidthEstimator.java
+++ b/src/java/com/android/internal/telephony/data/LinkBandwidthEstimator.java
@@ -32,8 +32,6 @@
 import android.os.HandlerExecutor;
 import android.os.Message;
 import android.os.OutcomeReceiver;
-import android.os.Registrant;
-import android.os.RegistrantList;
 import android.preference.PreferenceManager;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.Annotation.DataActivityType;
@@ -43,12 +41,14 @@
 import android.telephony.CellIdentityNr;
 import android.telephony.CellIdentityTdscdma;
 import android.telephony.CellIdentityWcdma;
+import android.telephony.CellInfo;
 import android.telephony.ModemActivityInfo;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.LocalLog;
@@ -97,6 +97,9 @@
     @VisibleForTesting
     static final int MSG_DATA_REG_STATE_OR_RAT_CHANGED = 9;
 
+    @VisibleForTesting
+    static final int UNKNOWN_TAC = CellInfo.UNAVAILABLE;
+
     // TODO: move the following parameters to xml file
     private static final int TRAFFIC_STATS_POLL_INTERVAL_MS = 1_000;
     private static final int MODEM_POLL_MIN_INTERVAL_MS = 5_000;
@@ -180,7 +183,7 @@
     private int mSignalLevel;
     private int mDataRat = TelephonyManager.NETWORK_TYPE_UNKNOWN;
     private int mTac;
-    private String mPlmn = UNKNOWN_PLMN;
+    @NonNull private String mPlmn = UNKNOWN_PLMN;
     private NetworkCapabilities mNetworkCapabilities;
     private NetworkBandwidth mPlaceholderNetwork;
     private long mFilterUpdateTimeMs;
@@ -191,7 +194,6 @@
     private String mBandwidthUpdatePlmn = UNKNOWN_PLMN;
     private BandwidthState mTxState = new BandwidthState(LINK_TX);
     private BandwidthState mRxState = new BandwidthState(LINK_RX);
-    private RegistrantList mBandwidthChangedRegistrants = new RegistrantList();
     private long mLastPlmnOrRatChangeTimeMs;
     private long mLastDrsOrRatChangeTimeMs;
 
@@ -351,33 +353,6 @@
     }
 
     /**
-     * Registers for bandwidth estimation change. The bandwidth will be returned
-     *      * {@link AsyncResult#result} as a {@link Pair} Object.
-     *      * The {@link AsyncResult} will be in the notification {@link Message#obj}.
-     * @param h handler to notify
-     * @param what what code of message when delivered
-     * @param obj placed in Message.obj
-     *
-     * @deprecated Use {@link #registerCallback(LinkBandwidthEstimatorCallback)}.
-     */
-    @Deprecated //TODO: Remove once old data stack is removed.
-    public void registerForBandwidthChanged(Handler h, int what, Object obj) {
-        Registrant r = new Registrant(h, what, obj);
-        mBandwidthChangedRegistrants.add(r);
-    }
-
-    /**
-     * Unregisters for bandwidth estimation change.
-     * @param h handler to notify
-     *
-     * @deprecated Use {@link #unregisterCallback(LinkBandwidthEstimatorCallback)}.
-     */
-    @Deprecated //TODO: Remove once old data stack is removed.
-    public void unregisterForBandwidthChanged(Handler h) {
-        mBandwidthChangedRegistrants.remove(h);
-    }
-
-    /**
      * Register the callback for receiving information from {@link LinkBandwidthEstimator}.
      *
      * @param callback The callback.
@@ -441,6 +416,7 @@
             return;
         }
         mIsOnDefaultRoute = isOnDefaultRoute;
+        logd("mIsOnDefaultRoute " + mIsOnDefaultRoute);
         handleTrafficStatsPollConditionChanged();
     }
 
@@ -465,6 +441,13 @@
         if (mScreenOn && mIsOnDefaultRoute && mIsOnActiveData) {
             updateDataRatCellIdentityBandwidth();
             handleTrafficStatsPoll();
+        } else {
+            logd("Traffic status poll stopped");
+            if (mDataActivity != TelephonyManager.DATA_ACTIVITY_NONE) {
+                mDataActivity = TelephonyManager.DATA_ACTIVITY_NONE;
+                mLinkBandwidthEstimatorCallbacks.forEach(callback -> callback.invokeFromExecutor(
+                        () -> callback.onDataActivityChanged(mDataActivity)));
+            }
         }
     }
 
@@ -475,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);
 
@@ -523,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) {
@@ -695,7 +678,7 @@
                 return;
             }
             int linkBandwidthKbps = (int) linkBandwidthLongKbps;
-            mBwSampleValid = true;
+            mBwSampleValid = linkBandwidthKbps > 0;
             mBwSampleKbps = linkBandwidthKbps;
 
             String dataRatName = getDataRatName(mDataRat);
@@ -715,7 +698,7 @@
             int filterInKbps = mBwSampleValid ? mBwSampleKbps : avgKbps;
 
             long currTimeMs = mTelephonyFacade.getElapsedSinceBootMillis();
-            int timeDeltaSec = (int) (currTimeMs - mBwSampleValidTimeMs) / 1000;
+            int timeDeltaSec = (int) ((currTimeMs - mBwSampleValidTimeMs) / 1000);
 
             // If the operation condition changes significantly since the last update
             // or the sample has higher BW, use a faster filter. Otherwise, use a slow filter
@@ -930,9 +913,6 @@
 
     private void sendLinkBandwidthToDataConnection(int linkBandwidthTxKps, int linkBandwidthRxKps) {
         logv("send to DC tx " + linkBandwidthTxKps + " rx " + linkBandwidthRxKps);
-        Pair<Integer, Integer> bandwidthInfo =
-                new Pair<Integer, Integer>(linkBandwidthTxKps, linkBandwidthRxKps);
-        mBandwidthChangedRegistrants.notifyRegistrants(new AsyncResult(null, bandwidthInfo, null));
         mLinkBandwidthEstimatorCallbacks.forEach(callback -> callback.invokeFromExecutor(
                 () -> callback.onBandwidthChanged(linkBandwidthTxKps, linkBandwidthRxKps)));
     }
@@ -1054,22 +1034,28 @@
     }
 
     private boolean updateDataRatCellIdentityBandwidth() {
-        boolean updatedPlmn = false;
-        CellIdentity cellIdentity = mPhone.getCurrentCellIdentity();
-        mTac = getTac(cellIdentity);
+        final ServiceState ss = mPhone.getServiceState();
+        final CellIdentity cellIdentity = mPhone.getCurrentCellIdentity();
+
+        boolean hasChanged = false;
         String plmn;
 
-        if (mPhone.getServiceState().getOperatorNumeric() != null) {
-            plmn = mPhone.getServiceState().getOperatorNumeric();
+        // Why does updating the TAC not result in hasChanged == true?
+        // Legacy behavior is currently being preserved for now, but it might very
+        // well be incorrect. The TAC is part of the network key. This is very fishy.
+        mTac = getTac(cellIdentity);
+
+        /* ss should always be non-null */
+        if (!TextUtils.isEmpty(ss.getOperatorNumeric())) {
+            plmn = ss.getOperatorNumeric();
+        } else if (cellIdentity != null && !TextUtils.isEmpty(cellIdentity.getPlmn())) {
+            plmn = cellIdentity.getPlmn();
         } else {
-            if (cellIdentity.getPlmn() != null) {
-                plmn = cellIdentity.getPlmn();
-            } else {
-                plmn = UNKNOWN_PLMN;
-            }
+            plmn = UNKNOWN_PLMN;
         }
-        if (mPlmn == null || !plmn.equals(mPlmn)) {
-            updatedPlmn = true;
+
+        if (!mPlmn.equals(plmn)) {
+            hasChanged = true;
             mPlmn = plmn;
         }
 
@@ -1078,20 +1064,19 @@
         if (nri != null) {
             int dataRat = nri.getAccessNetworkTechnology();
             if (dataRat != mDataRat) {
-                updatedRat = true;
+                hasChanged = true;
                 mDataRat = dataRat;
                 updateStaticBwValue(mDataRat);
                 updateByteCountThr();
             }
         }
 
-        boolean updatedPlmnOrRat = updatedPlmn || updatedRat;
-        if (updatedPlmnOrRat) {
+        if (hasChanged) {
             resetBandwidthFilter();
             updateTxRxBandwidthFilterSendToDataConnection();
             mLastPlmnOrRatChangeTimeMs = mTelephonyFacade.getElapsedSinceBootMillis();
         }
-        return updatedPlmnOrRat;
+        return hasChanged;
     }
 
     private int getTac(@NonNull CellIdentity cellIdentity) {
@@ -1110,7 +1095,7 @@
         if (cellIdentity instanceof CellIdentityGsm) {
             return ((CellIdentityGsm) cellIdentity).getLac();
         }
-        return 0;
+        return UNKNOWN_TAC;
     }
 
     private class TelephonyCallbackImpl extends TelephonyCallback implements
@@ -1135,16 +1120,17 @@
         mLocalLog.log(msg);
     }
 
-    @VisibleForTesting
-    static final int UNKNOWN_TAC = -1;
     // Map with NetworkKey as the key and NetworkBandwidth as the value.
     // NetworkKey is specified by the PLMN, data RAT and TAC of network.
     // NetworkBandwidth represents the bandwidth related stats of each network.
     private final Map<NetworkKey, NetworkBandwidth> mNetworkMap = new ArrayMap<>();
+
     private static class NetworkKey {
+
         private final String mPlmn;
         private final String mDataRat;
         private final int mTac;
+
         NetworkKey(String plmn, int tac, String dataRat) {
             mPlmn = plmn;
             mTac = tac;
@@ -1175,22 +1161,21 @@
             StringBuilder sb = new StringBuilder();
             sb.append("Plmn").append(mPlmn)
                     .append("Rat").append(mDataRat)
-                    .append("Tac").append(mTac)
-                    .toString();
+                    .append("Tac").append(mTac);
             return sb.toString();
         }
     }
 
     @NonNull
-    private NetworkBandwidth lookupNetwork(String plmn, String dataRat) {
+    private NetworkBandwidth lookupNetwork(@NonNull String plmn, String dataRat) {
         return lookupNetwork(plmn, UNKNOWN_TAC, dataRat);
     }
 
     /** Look up NetworkBandwidth and create a new one if it doesn't exist */
     @VisibleForTesting
     @NonNull
-    public NetworkBandwidth lookupNetwork(String plmn, int tac, String dataRat) {
-        if (plmn == null || dataRat.equals(
+    public NetworkBandwidth lookupNetwork(@NonNull String plmn, int tac, String dataRat) {
+        if (dataRat.equals(
                 TelephonyManager.getNetworkTypeName(TelephonyManager.NETWORK_TYPE_UNKNOWN))) {
             return mPlaceholderNetwork;
         }
@@ -1206,7 +1191,9 @@
     /** A class holding link bandwidth related stats */
     @VisibleForTesting
     public class NetworkBandwidth {
+
         private final String mKey;
+
         NetworkBandwidth(String key) {
             mKey = key;
         }
@@ -1302,5 +1289,4 @@
         pw.println();
         pw.flush();
     }
-
 }
diff --git a/src/java/com/android/internal/telephony/data/NotifyQosSessionInterface.java b/src/java/com/android/internal/telephony/data/NotifyQosSessionInterface.java
deleted file mode 100644
index 554177a..0000000
--- a/src/java/com/android/internal/telephony/data/NotifyQosSessionInterface.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.data;
-
-import android.annotation.NonNull;
-import android.net.NetworkAgent;
-import android.net.QosSessionAttributes;
-
-/**
- * The temporary interface that is shared by
- * {@link com.android.internal.telephony.dataconnection.DcNetworkAgent} and
- * {@link com.android.internal.telephony.data.TelephonyNetworkAgent} so they can both interact
- * with {@link QosCallbackTracker}.
- */
-// TODO: Remove after DcNetworkAgent is removed.
-public interface NotifyQosSessionInterface {
-    /**
-     * Sends the attributes of Qos Session back to the Application. This method is create for
-     * Mockito to mock since
-     * {@link NetworkAgent#sendQosSessionAvailable(int, int, QosSessionAttributes)} is
-     * {@code final} that can't be mocked.
-     *
-     * @param qosCallbackId the callback id that the session belongs to.
-     * @param sessionId the unique session id across all Qos Sessions.
-     * @param attributes the attributes of the Qos Session.
-     */
-    void notifyQosSessionAvailable(int qosCallbackId, int sessionId,
-            @NonNull QosSessionAttributes attributes);
-
-    /**
-     * Sends event that the Qos Session was lost. This method is create for Mockito to mock
-     * since {@link NetworkAgent#sendQosSessionLost(int, int, int)} is {@code final} that can't be
-     * mocked..
-     *
-     * @param qosCallbackId the callback id that the session belongs to.
-     * @param sessionId the unique session id across all Qos Sessions.
-     * @param qosSessionType the session type {@code QosSession#QosSessionType}.
-     */
-    void notifyQosSessionLost(int qosCallbackId, int sessionId, int qosSessionType);
-}
diff --git a/src/java/com/android/internal/telephony/data/PhoneSwitcher.java b/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
index cc5f447..5e13f6b 100644
--- a/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
+++ b/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
@@ -31,6 +31,10 @@
 import static java.util.Arrays.copyOf;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -46,6 +50,7 @@
 import android.net.TelephonyNetworkSpecifier;
 import android.os.AsyncResult;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -53,20 +58,23 @@
 import android.os.Registrant;
 import android.os.RegistrantList;
 import android.os.RemoteException;
+import android.provider.Settings;
+import android.telephony.AccessNetworkConstants;
 import android.telephony.CarrierConfigManager;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhoneCapability;
 import android.telephony.PhoneStateListener;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.TelephonyRegistryManager;
-import android.telephony.data.ApnSetting;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ImsRegistrationAttributes;
 import android.telephony.ims.RegistrationManager;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.util.ArrayMap;
 import android.util.LocalLog;
+import android.util.Log;
 
 import com.android.ims.ImsException;
 import com.android.ims.ImsManager;
@@ -78,17 +86,17 @@
 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;
-import com.android.internal.telephony.dataconnection.ApnConfigTypeRepository;
-import com.android.internal.telephony.dataconnection.DcRequest;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent;
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent.DataSwitch;
 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;
 
@@ -96,7 +104,6 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Calendar;
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -112,9 +119,23 @@
  */
 public class PhoneSwitcher extends Handler {
     private static final String LOG_TAG = "PhoneSwitcher";
-    protected static final boolean VDBG = false;
+    protected static final boolean VDBG = Rlog.isLoggable(LOG_TAG, Log.VERBOSE);
 
-    private static final int DEFAULT_NETWORK_CHANGE_TIMEOUT_MS = 5000;
+    /** Fragment "key" argument passed thru {@link #SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS} */
+    private static final String SETTINGS_EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
+    /**
+     * When starting this activity, this extra can also be specified to supply a Bundle of arguments
+     * to pass to that fragment when it is instantiated during the initial creation of the activity.
+     */
+    private static final String SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS =
+            ":settings:show_fragment_args";
+    /** The res Id of the auto data switch fragment in settings. **/
+    private static final String AUTO_DATA_SWITCH_SETTING_R_ID = "auto_data_switch";
+    /** Notification tag **/
+    private static final String AUTO_DATA_SWITCH_NOTIFICATION_TAG = "auto_data_switch";
+    /** Notification ID **/
+    private static final int AUTO_DATA_SWITCH_NOTIFICATION_ID = 1;
+
     private static final int MODEM_COMMAND_RETRY_PERIOD_MS     = 5000;
     // After the emergency call ends, wait for a few seconds to see if we enter ECBM before starting
     // the countdown to remove the emergency DDS override.
@@ -182,10 +203,9 @@
         }
     }
 
-    protected final List<DcRequest> mPrioritizedDcRequests = new ArrayList<>();
     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;
     protected PhoneState[] mPhoneStates;
@@ -194,6 +214,10 @@
     @VisibleForTesting
     protected final CellularNetworkValidator mValidator;
     private int mPendingSwitchSubId = INVALID_SUBSCRIPTION_ID;
+    /** The reason for the last time changing preferred data sub **/
+    private int mLastSwitchPreferredDataReason = -1;
+    /** {@code true} if we've displayed the notification the first time auto switch occurs **/
+    private boolean mDisplayedAutoSwitchNotification = false;
     private boolean mPendingSwitchNeedValidation;
     @VisibleForTesting
     public final CellularNetworkValidator.ValidationCallback mValidationCallback =
@@ -226,9 +250,15 @@
     // Internet data if mOpptDataSubId is not set.
     protected int mPrimaryDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
-    // mOpptDataSubId must be an active subscription. If it's set, it overrides mPrimaryDataSubId
-    // to be used for Internet data.
-    private int mOpptDataSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+    // The automatically suggested preferred data subId (by e.g. CBRS or auto data switch), a
+    // candidate for preferred data subId, which is eventually presided by
+    // updatePreferredDataPhoneId().
+    // If CBRS/auto switch feature selects the primary data subId as the preferred data subId,
+    // its value will be DEFAULT_SUBSCRIPTION_ID.
+    private int mAutoSelectedDataSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+
+    /** The count of consecutive auto switch validation failure **/
+    private int mAutoSwitchRetryFailedCount = 0;
 
     // The phone ID that has an active voice call. If set, and its mobile data setting is on,
     // it will become the mPreferredDataPhoneId.
@@ -244,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);
-            SubscriptionController.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.
@@ -261,7 +285,7 @@
     private ISetOpportunisticDataCallback mSetOpptSubCallback;
 
     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
@@ -276,14 +300,12 @@
     // mEmergencyOverride, start the countdown to remove the override using the message
     // EVENT_REMOVE_DDS_EMERGENCY_OVERRIDE. The only exception to this is if the device moves to
     // ECBM, which is detected by EVENT_EMERGENCY_TOGGLE.
-    @VisibleForTesting
-    public static final int EVENT_PRECISE_CALL_STATE_CHANGED      = 109;
+    private static final int EVENT_PRECISE_CALL_STATE_CHANGED     = 109;
     private static final int EVENT_NETWORK_VALIDATION_DONE        = 110;
-    private static final int EVENT_REMOVE_DEFAULT_NETWORK_CHANGE_CALLBACK = 111;
+    private static final int EVENT_EVALUATE_AUTO_SWITCH           = 111;
     private static final int EVENT_MODEM_COMMAND_DONE             = 112;
     private static final int EVENT_MODEM_COMMAND_RETRY            = 113;
-    @VisibleForTesting
-    public static final int EVENT_DATA_ENABLED_CHANGED            = 114;
+    private static final int EVENT_SERVICE_STATE_CHANGED          = 114;
     // An emergency call is about to be originated and requires the DDS to be overridden.
     // Uses EVENT_PRECISE_CALL_STATE_CHANGED message to start countdown to finish override defined
     // in mEmergencyOverride. If EVENT_PRECISE_CALL_STATE_CHANGED does not come in
@@ -292,12 +314,11 @@
     // If it exists, remove the current mEmergencyOverride DDS override.
     private static final int EVENT_REMOVE_DDS_EMERGENCY_OVERRIDE  = 116;
     // If it exists, remove the current mEmergencyOverride DDS override.
-    @VisibleForTesting
-    public static final int EVENT_MULTI_SIM_CONFIG_CHANGED        = 117;
+    private static final int EVENT_MULTI_SIM_CONFIG_CHANGED       = 117;
     private static final int EVENT_NETWORK_AVAILABLE              = 118;
     private static final int EVENT_PROCESS_SIM_STATE_CHANGE       = 119;
-    @VisibleForTesting
-    public static final int EVENT_IMS_RADIO_TECH_CHANGED          = 120;
+    private static final int EVENT_IMS_RADIO_TECH_CHANGED         = 120;
+    private static final int EVENT_MEETS_AUTO_DATA_SWITCH_STATE   = 121;
 
     // List of events triggers re-evaluations
     private static final String EVALUATION_REASON_RADIO_ON = "EVENT_RADIO_ON";
@@ -317,13 +338,29 @@
     // Default timeout value of network validation in millisecond.
     private final static int DEFAULT_VALIDATION_EXPIRATION_TIME = 2000;
 
-    private Boolean mHasRegisteredDefaultNetworkChangeCallback = false;
-
     private ConnectivityManager mConnectivityManager;
     private int mImsRegistrationTech = REGISTRATION_TECH_NONE;
 
     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.
+     */
+    private long mAutoDataSwitchAvailabilityStabilityTimeThreshold = -1;
+
+    /**
+     * The maximum number of retries when a validation for switching failed.
+     */
+    private int mAutoDataSwitchValidationMaxRetry;
+
     /** Data settings manager callback. Key is the phone id. */
     private final @NonNull Map<Integer, DataSettingsManagerCallback> mDataSettingsManagerCallbacks =
             new ArrayMap<>();
@@ -331,18 +368,37 @@
     private class DefaultNetworkCallback extends ConnectivityManager.NetworkCallback {
         public int mExpectedSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
         public int mSwitchReason = TelephonyEvent.DataSwitch.Reason.DATA_SWITCH_REASON_UNKNOWN;
+        public boolean isDefaultNetworkOnCellular = false;
         @Override
         public void onCapabilitiesChanged(Network network,
                 NetworkCapabilities networkCapabilities) {
-            if (networkCapabilities.hasTransport(TRANSPORT_CELLULAR)
-                    && SubscriptionManager.isValidSubscriptionId(mExpectedSubId)
-                    && mExpectedSubId == getSubIdFromNetworkSpecifier(
-                            networkCapabilities.getNetworkSpecifier())) {
-                logDataSwitchEvent(
-                        mExpectedSubId,
-                        TelephonyEvent.EventState.EVENT_STATE_END,
-                        mSwitchReason);
-                removeDefaultNetworkChangeCallback();
+            if (networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
+                isDefaultNetworkOnCellular = true;
+                if (SubscriptionManager.isValidSubscriptionId(mExpectedSubId)
+                        && mExpectedSubId == getSubIdFromNetworkSpecifier(
+                        networkCapabilities.getNetworkSpecifier())) {
+                    logDataSwitchEvent(
+                            mExpectedSubId,
+                            TelephonyEvent.EventState.EVENT_STATE_END,
+                            mSwitchReason);
+                    mExpectedSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+                    mSwitchReason = TelephonyEvent.DataSwitch.Reason.DATA_SWITCH_REASON_UNKNOWN;
+                }
+            } else {
+                if (isDefaultNetworkOnCellular) {
+                    // non-cellular transport is active
+                    isDefaultNetworkOnCellular = false;
+                    log("default network is active on non cellular");
+                    evaluateIfAutoSwitchIsNeeded();
+                }
+            }
+        }
+
+        @Override
+        public void onLost(Network network) {
+            // try find an active sub to switch to
+            if (!hasMessages(EVENT_EVALUATE_AUTO_SWITCH)) {
+                sendEmptyMessage(EVENT_EVALUATE_AUTO_SWITCH);
             }
         }
     }
@@ -395,49 +451,26 @@
     public static PhoneSwitcher make(int maxDataAttachModemCount, Context context, Looper looper) {
         if (sPhoneSwitcher == null) {
             sPhoneSwitcher = new PhoneSwitcher(maxDataAttachModemCount, context, looper);
-            SubscriptionController.invalidateActiveDataSubIdCaches();
         }
 
         return sPhoneSwitcher;
     }
 
-    /**
-     * Whether this phone IMS registration is on its original network. This result impacts
-     * whether we want to do DDS switch to the phone having voice call.
-     * If it's registered on IWLAN or cross SIM in multi-SIM case, return false. Otherwise,
-     * return true.
-     */
-    private boolean isImsOnOriginalNetwork(Phone phone) {
-        if (phone == null) return false;
-        int phoneId = phone.getPhoneId();
-        if (!SubscriptionManager.isValidPhoneId(phoneId)) return false;
-
-        int imsRegTech = mImsRegTechProvider.get(mContext, phoneId);
-        // If IMS is registered on IWLAN or cross SIM, return false.
-        boolean isOnOriginalNetwork = (imsRegTech != REGISTRATION_TECH_IWLAN)
-                && (imsRegTech != REGISTRATION_TECH_CROSS_SIM);
-        if (!isOnOriginalNetwork) {
-            log("IMS call on IWLAN or cross SIM. Call will be ignored for DDS switch");
-        }
-        return isOnOriginalNetwork;
-    }
-
-    private boolean isPhoneInVoiceCallChanged() {
+    private boolean updatesIfPhoneInVoiceCallChanged() {
         int oldPhoneIdInVoiceCall = mPhoneIdInVoiceCall;
         // If there's no active call, the value will become INVALID_PHONE_INDEX
         // and internet data will be switched back to system selected or user selected
         // subscription.
         mPhoneIdInVoiceCall = SubscriptionManager.INVALID_PHONE_INDEX;
         for (Phone phone : PhoneFactory.getPhones()) {
-            if (isPhoneInVoiceCall(phone) || (isPhoneInVoiceCall(phone.getImsPhone())
-                    && isImsOnOriginalNetwork(phone))) {
+            if (isPhoneInVoiceCall(phone) || isPhoneInVoiceCall(phone.getImsPhone())) {
                 mPhoneIdInVoiceCall = phone.getPhoneId();
                 break;
             }
         }
 
         if (mPhoneIdInVoiceCall != oldPhoneIdInVoiceCall) {
-            log("isPhoneInVoiceCallChanged from phoneId " + oldPhoneIdInVoiceCall
+            logl("isPhoneInVoiceCallChanged from phoneId " + oldPhoneIdInVoiceCall
                     + " to phoneId " + mPhoneIdInVoiceCall);
             return true;
         } else {
@@ -464,13 +497,13 @@
         }
     }
 
-    private void evaluateIfDataSwitchIsNeeded(String reason) {
-        if (onEvaluate(REQUESTS_UNCHANGED, reason)) {
+    private void evaluateIfImmediateDataSwitchIsNeeded(String evaluationReason, int switchReason) {
+        if (onEvaluate(REQUESTS_UNCHANGED, evaluationReason)) {
             logDataSwitchEvent(mPreferredDataSubId.get(),
                     TelephonyEvent.EventState.EVENT_STATE_START,
-                    DataSwitch.Reason.DATA_SWITCH_REASON_IN_CALL);
+                    switchReason);
             registerDefaultNetworkChangeCallback(mPreferredDataSubId.get(),
-                    DataSwitch.Reason.DATA_SWITCH_REASON_IN_CALL);
+                    switchReason);
         }
     }
 
@@ -484,7 +517,8 @@
         mMaxDataAttachModemCount = maxActivePhones;
         mLocalLog = new LocalLog(MAX_LOCAL_LOG_LINES);
 
-        mSubscriptionController = SubscriptionController.getInstance();
+        mSubscriptionManagerService = SubscriptionManagerService.getInstance();
+
         mRadioConfig = RadioConfig.getInstance();
         mValidator = CellularNetworkValidator.getInstance();
 
@@ -494,9 +528,9 @@
         mContext.registerReceiver(mSimStateIntentReceiver, filter);
 
         mActivePhoneRegistrants = new RegistrantList();
-        for (int i = 0; i < mActiveModemCount; i++) {
-            mPhoneStates[i] = new PhoneState();
-            Phone phone = PhoneFactory.getPhone(i);
+        for (int phoneId = 0; phoneId < mActiveModemCount; phoneId++) {
+            mPhoneStates[phoneId] = new PhoneState();
+            Phone phone = PhoneFactory.getPhone(phoneId);
             if (phone != null) {
                 phone.registerForEmergencyCallToggle(
                         this, EVENT_EMERGENCY_TOGGLE, null);
@@ -507,23 +541,19 @@
                     phone.getImsPhone().registerForPreciseCallStateChanged(
                             this, EVENT_PRECISE_CALL_STATE_CHANGED, null);
                 }
-                if (phone.isUsingNewDataStack()) {
-                    mDataSettingsManagerCallbacks.computeIfAbsent(phone.getPhoneId(),
-                            v -> new DataSettingsManagerCallback(this::post) {
-                                @Override
-                                public void onDataEnabledChanged(boolean enabled,
-                                        @TelephonyManager.DataEnabledChangedReason int reason,
-                                        @NonNull String callingPackage) {
-                                    evaluateIfDataSwitchIsNeeded("EVENT_DATA_ENABLED_CHANGED");
-                                }});
-                    phone.getDataSettingsManager().registerCallback(
-                            mDataSettingsManagerCallbacks.get(phone.getPhoneId()));
-                } else {
-                    phone.getDataEnabledSettings().registerForDataEnabledChanged(
-                            this, EVENT_DATA_ENABLED_CHANGED, null);
-                }
-
-                registerForImsRadioTechChange(context, i);
+                mDataSettingsManagerCallbacks.computeIfAbsent(phoneId,
+                        v -> new DataSettingsManagerCallback(this::post) {
+                            @Override
+                            public void onDataEnabledChanged(boolean enabled,
+                                    @TelephonyManager.DataEnabledChangedReason int reason,
+                                    @NonNull String callingPackage) {
+                                PhoneSwitcher.this.onDataEnabledChanged();
+                            }});
+                phone.getDataSettingsManager().registerCallback(
+                        mDataSettingsManagerCallbacks.get(phoneId));
+                phone.getServiceStateTracker().registerForServiceStateChanged(this,
+                        EVENT_SERVICE_STATE_CHANGED, phoneId);
+                registerForImsRadioTechChange(context, phoneId);
             }
             Set<CommandException.Error> ddsFailure = new HashSet<CommandException.Error>();
             mCurrentDdsSwitchFailure.add(ddsFailure);
@@ -533,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(
@@ -547,6 +579,8 @@
         PhoneConfigurationManager.registerForMultiSimConfigChange(
                 this, EVENT_MULTI_SIM_CONFIG_CHANGED, null);
 
+        mConnectivityManager.registerDefaultNetworkCallback(mDefaultNetworkCallback, this);
+
         final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder()
                 .addTransportType(TRANSPORT_CELLULAR)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
@@ -580,7 +614,7 @@
 
         updateHalCommandToUse();
 
-        log("PhoneSwitcher started");
+        logl("PhoneSwitcher started");
     }
 
     private final BroadcastReceiver mDefaultDataChangedReceiver = new BroadcastReceiver() {
@@ -600,7 +634,7 @@
                         TelephonyManager.SIM_STATE_UNKNOWN);
                 int slotIndex = intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX,
                         SubscriptionManager.INVALID_SIM_SLOT_INDEX);
-                log("mSimStateIntentReceiver: slotIndex = " + slotIndex + " state = " + state);
+                logl("mSimStateIntentReceiver: slotIndex = " + slotIndex + " state = " + state);
                 obtainMessage(EVENT_PROCESS_SIM_STATE_CHANGE, slotIndex, state).sendToTarget();
             }
         }
@@ -611,14 +645,14 @@
             return false;
         }
 
-        SubscriptionInfo info = SubscriptionController.getInstance()
+        SubscriptionInfo info = mSubscriptionManagerService
                 .getActiveSubscriptionInfoForSimSlotIndex(slotIndex,
-                mContext.getOpPackageName(), null);
+                        mContext.getOpPackageName(), mContext.getAttributionTag());
         boolean uiccAppsEnabled = info != null && info.areUiccApplicationsEnabled();
 
         IccCard iccCard = PhoneFactory.getPhone(slotIndex).getIccCard();
         if (!iccCard.isEmptyProfile() && uiccAppsEnabled) {
-            log("isSimApplicationReady: SIM is ready for slotIndex: " + slotIndex);
+            logl("isSimApplicationReady: SIM is ready for slotIndex: " + slotIndex);
             return true;
         } else {
             return false;
@@ -638,17 +672,25 @@
     public void handleMessage(Message msg) {
         switch (msg.what) {
             case EVENT_SUBSCRIPTION_CHANGED: {
-                onEvaluate(REQUESTS_UNCHANGED, "subChanged");
+                onEvaluate(REQUESTS_UNCHANGED, "subscription changed");
+                break;
+            }
+            case EVENT_SERVICE_STATE_CHANGED: {
+                AsyncResult ar = (AsyncResult) msg.obj;
+                final int phoneId = (int) ar.userObj;
+                onServiceStateChanged(phoneId);
+                break;
+            }
+            case EVENT_MEETS_AUTO_DATA_SWITCH_STATE: {
+                final int targetSubId = msg.arg1;
+                final boolean needValidation = (boolean) msg.obj;
+                validate(targetSubId, needValidation,
+                        DataSwitch.Reason.DATA_SWITCH_REASON_AUTO, null);
                 break;
             }
             case EVENT_PRIMARY_DATA_SUB_CHANGED: {
-                if (onEvaluate(REQUESTS_UNCHANGED, "primary data subId changed")) {
-                    logDataSwitchEvent(mPreferredDataSubId.get(),
-                            TelephonyEvent.EventState.EVENT_STATE_START,
-                            DataSwitch.Reason.DATA_SWITCH_REASON_MANUAL);
-                    registerDefaultNetworkChangeCallback(mPreferredDataSubId.get(),
-                            DataSwitch.Reason.DATA_SWITCH_REASON_MANUAL);
-                }
+                evaluateIfImmediateDataSwitchIsNeeded("primary data sub changed",
+                        DataSwitch.Reason.DATA_SWITCH_REASON_MANUAL);
                 break;
             }
             case EVENT_REQUEST_NETWORK: {
@@ -662,7 +704,7 @@
             case EVENT_EMERGENCY_TOGGLE: {
                 boolean isInEcm = isInEmergencyCallbackMode();
                 if (mEmergencyOverride != null) {
-                    log("Emergency override - ecbm status = " + isInEcm);
+                    logl("Emergency override - ecbm status = " + isInEcm);
                     if (isInEcm) {
                         // The device has gone into ECBM. Wait until it's out.
                         removeMessages(EVENT_REMOVE_DDS_EMERGENCY_OVERRIDE);
@@ -676,6 +718,9 @@
                 onEvaluate(REQUESTS_CHANGED, "emergencyToggle");
                 break;
             }
+            case EVENT_EVALUATE_AUTO_SWITCH:
+                evaluateIfAutoSwitchIsNeeded();
+                break;
             case EVENT_RADIO_CAPABILITY_CHANGED: {
                 final int phoneId = msg.arg1;
                 sendRilCommands(phoneId);
@@ -694,24 +739,25 @@
                 onEvaluate(REQUESTS_UNCHANGED, EVALUATION_REASON_RADIO_ON);
                 break;
             }
-            case EVENT_IMS_RADIO_TECH_CHANGED:
+            case EVENT_IMS_RADIO_TECH_CHANGED: {
                 // register for radio tech change to listen to radio tech handover in case previous
                 // attempt was not successful
                 registerForImsRadioTechChange();
-                // If the phoneId in voice call didn't change, do nothing.
-                if (!isPhoneInVoiceCallChanged()) {
-                    break;
+                // if voice call state changes or in voice call didn't change
+                // but RAT changes(e.g. Iwlan -> cross sim), reevaluate for data switch.
+                if (updatesIfPhoneInVoiceCallChanged() || isAnyVoiceCallActiveOnDevice()) {
+                    evaluateIfImmediateDataSwitchIsNeeded("Ims radio tech changed",
+                            DataSwitch.Reason.DATA_SWITCH_REASON_IN_CALL);
                 }
-                evaluateIfDataSwitchIsNeeded("EVENT_IMS_RADIO_TECH_CHANGED");
                 break;
-
+            }
             case EVENT_PRECISE_CALL_STATE_CHANGED: {
                 // register for radio tech change to listen to radio tech handover in case previous
                 // attempt was not successful
                 registerForImsRadioTechChange();
 
                 // If the phoneId in voice call didn't change, do nothing.
-                if (!isPhoneInVoiceCallChanged()) {
+                if (!updatesIfPhoneInVoiceCallChanged()) {
                     break;
                 }
 
@@ -740,13 +786,16 @@
                         mEmergencyOverride.mPendingOriginatingCall = false;
                     }
                 }
-                evaluateIfDataSwitchIsNeeded("EVENT_PRECISE_CALL_STATE_CHANGED");
+                // Always update data modem via data during call code path, because
+                // mAutoSelectedDataSubId doesn't know about any data switch due to voice call
+                evaluateIfImmediateDataSwitchIsNeeded("precise call state changed",
+                        DataSwitch.Reason.DATA_SWITCH_REASON_IN_CALL);
+                if (!isAnyVoiceCallActiveOnDevice()) {
+                    // consider auto switch on hang up all voice call
+                    evaluateIfAutoSwitchIsNeeded();
+                }
                 break;
             }
-
-            case EVENT_DATA_ENABLED_CHANGED:
-                evaluateIfDataSwitchIsNeeded("EVENT_DATA_ENABLED_CHANGED");
-                break;
             case EVENT_NETWORK_VALIDATION_DONE: {
                 int subId = msg.arg1;
                 boolean passed = (msg.arg2 == 1);
@@ -759,10 +808,6 @@
                 onNetworkAvailable(subId, network);
                 break;
             }
-            case EVENT_REMOVE_DEFAULT_NETWORK_CHANGE_CALLBACK: {
-                removeDefaultNetworkChangeCallback();
-                break;
-            }
             case EVENT_MODEM_COMMAND_DONE: {
                 AsyncResult ar = (AsyncResult) msg.obj;
                 onDdsSwitchResponse(ar);
@@ -770,11 +815,14 @@
             }
             case EVENT_MODEM_COMMAND_RETRY: {
                 int phoneId = (int) msg.obj;
+                if (mActiveModemCount <= phoneId) {
+                    break;
+                }
                 if (isPhoneIdValidForRetry(phoneId)) {
-                    log("EVENT_MODEM_COMMAND_RETRY: resend modem command on phone " + phoneId);
+                    logl("EVENT_MODEM_COMMAND_RETRY: resend modem command on phone " + phoneId);
                     sendRilCommands(phoneId);
                 } else {
-                    log("EVENT_MODEM_COMMAND_RETRY: skip retry as DDS sub changed");
+                    logl("EVENT_MODEM_COMMAND_RETRY: skip retry as DDS sub changed");
                     mCurrentDdsSwitchFailure.get(phoneId).clear();
                 }
                 break;
@@ -786,7 +834,7 @@
                     // being overridden, ignore. We should not try to switch DDS while already
                     // waiting for SUPL.
                     if (mEmergencyOverride.mPhoneId != req.mPhoneId) {
-                        log("emergency override requested for phone id " + req.mPhoneId + " when "
+                        logl("emergency override requested for phone id " + req.mPhoneId + " when "
                                 + "there is already an override in place for phone id "
                                 + mEmergencyOverride.mPhoneId + ". Ignoring.");
                         if (req.isCallbackAvailable()) {
@@ -806,7 +854,7 @@
                     mEmergencyOverride = req;
                 }
 
-                log("new emergency override - " + mEmergencyOverride);
+                logl("new emergency override - " + mEmergencyOverride);
                 // a new request has been created, remove any previous override complete scheduled.
                 removeMessages(EVENT_REMOVE_DDS_EMERGENCY_OVERRIDE);
                 Message msg2 = obtainMessage(EVENT_REMOVE_DDS_EMERGENCY_OVERRIDE);
@@ -824,7 +872,7 @@
                 break;
             }
             case EVENT_REMOVE_DDS_EMERGENCY_OVERRIDE: {
-                log("Emergency override removed - " + mEmergencyOverride);
+                logl("Emergency override removed - " + mEmergencyOverride);
                 mEmergencyOverride = null;
                 onEvaluate(REQUESTS_UNCHANGED, "emer_rm_override_dds");
                 break;
@@ -835,23 +883,42 @@
                 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)) {
-                    log("EVENT_PROCESS_SIM_STATE_CHANGE: skip processing due to invalid slotId: "
+                    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);
                 }
                 break;
             }
         }
     }
 
+    /**
+     * Read the default device config from any default phone because the resource config are per
+     * device. No need to register callback for the same reason.
+     */
+    private void readDeviceResourceConfig() {
+        Phone phone = PhoneFactory.getDefaultPhone();
+        DataConfigManager dataConfig = phone.getDataNetworkController().getDataConfigManager();
+        mRequirePingTestBeforeDataSwitch = dataConfig.isPingTestBeforeAutoDataSwitchRequired();
+        mAutoDataSwitchAvailabilityStabilityTimeThreshold =
+                dataConfig.getAutoDataSwitchAvailabilityStabilityTimeThreshold();
+        mAutoDataSwitchValidationMaxRetry =
+                dataConfig.getAutoDataSwitchValidationMaxRetry();
+    }
+
     private synchronized void onMultiSimConfigChanged(int activeModemCount) {
         // No change.
         if (mActiveModemCount == activeModemCount) return;
@@ -880,22 +947,19 @@
                         this, EVENT_PRECISE_CALL_STATE_CHANGED, null);
             }
 
-            if (phone.isUsingNewDataStack()) {
-                mDataSettingsManagerCallbacks.computeIfAbsent(phone.getPhoneId(),
-                        v -> new DataSettingsManagerCallback(this::post) {
-                            @Override
-                            public void onDataEnabledChanged(boolean enabled,
-                                    @TelephonyManager.DataEnabledChangedReason int reason,
-                                    @NonNull String callingPackage) {
-                                evaluateIfDataSwitchIsNeeded("EVENT_DATA_ENABLED_CHANGED");
-                            }
-                        });
-                phone.getDataSettingsManager().registerCallback(
-                        mDataSettingsManagerCallbacks.get(phone.getPhoneId()));
-            } else {
-                phone.getDataEnabledSettings().registerForDataEnabledChanged(
-                        this, EVENT_DATA_ENABLED_CHANGED, null);
-            }
+            mDataSettingsManagerCallbacks.computeIfAbsent(phone.getPhoneId(),
+                    v -> new DataSettingsManagerCallback(this::post) {
+                        @Override
+                        public void onDataEnabledChanged(boolean enabled,
+                                @TelephonyManager.DataEnabledChangedReason int reason,
+                                @NonNull String callingPackage) {
+                            PhoneSwitcher.this.onDataEnabledChanged();
+                        }
+                    });
+            phone.getDataSettingsManager().registerCallback(
+                    mDataSettingsManagerCallbacks.get(phone.getPhoneId()));
+            phone.getServiceStateTracker().registerForServiceStateChanged(this,
+                    EVENT_SERVICE_STATE_CHANGED, phoneId);
 
             Set<CommandException.Error> ddsFailure = new HashSet<CommandException.Error>();
             mCurrentDdsSwitchFailure.add(ddsFailure);
@@ -903,6 +967,23 @@
         }
     }
 
+    /**
+     * Called when
+     * 1. user changed mobile data settings
+     * 2. OR user changed auto data switch feature
+     */
+    private void onDataEnabledChanged() {
+        logl("user changed data related settings");
+        if (isAnyVoiceCallActiveOnDevice()) {
+            // user changed data related settings during call, switch or turn off immediately
+            evaluateIfImmediateDataSwitchIsNeeded(
+                    "user changed data settings during call",
+                    DataSwitch.Reason.DATA_SWITCH_REASON_IN_CALL);
+        } else {
+            evaluateIfAutoSwitchIsNeeded();
+        }
+    }
+
     private boolean isInEmergencyCallbackMode() {
         for (Phone p : PhoneFactory.getPhones()) {
             if (p == null) continue;
@@ -941,79 +1022,26 @@
     }
 
     private void onRequestNetwork(NetworkRequest networkRequest) {
-        if (PhoneFactory.getDefaultPhone().isUsingNewDataStack()) {
-            TelephonyNetworkRequest telephonyNetworkRequest = new TelephonyNetworkRequest(
-                    networkRequest, PhoneFactory.getDefaultPhone());
-            if (!mNetworkRequestList.contains(telephonyNetworkRequest)) {
-                mNetworkRequestList.add(telephonyNetworkRequest);
-                onEvaluate(REQUESTS_CHANGED, "netRequest");
-            }
-            return;
-        }
-        final DcRequest dcRequest =
-                DcRequest.create(networkRequest, createApnRepository(networkRequest));
-        if (dcRequest != null) {
-            if (!mPrioritizedDcRequests.contains(dcRequest)) {
-                collectRequestNetworkMetrics(networkRequest);
-                mPrioritizedDcRequests.add(dcRequest);
-                Collections.sort(mPrioritizedDcRequests);
-                onEvaluate(REQUESTS_CHANGED, "netRequest");
-                if (VDBG) log("Added DcRequest, size: " + mPrioritizedDcRequests.size());
-            }
+        TelephonyNetworkRequest telephonyNetworkRequest = new TelephonyNetworkRequest(
+                networkRequest, PhoneFactory.getDefaultPhone());
+        if (!mNetworkRequestList.contains(telephonyNetworkRequest)) {
+            mNetworkRequestList.add(telephonyNetworkRequest);
+            onEvaluate(REQUESTS_CHANGED, "netRequest");
         }
     }
 
     private void onReleaseNetwork(NetworkRequest networkRequest) {
-        if (PhoneFactory.getDefaultPhone().isUsingNewDataStack()) {
-            TelephonyNetworkRequest telephonyNetworkRequest = new TelephonyNetworkRequest(
-                    networkRequest, PhoneFactory.getDefaultPhone());
-            if (mNetworkRequestList.remove(telephonyNetworkRequest)) {
-                onEvaluate(REQUESTS_CHANGED, "netReleased");
-                collectReleaseNetworkMetrics(networkRequest);
-            }
-            return;
+        TelephonyNetworkRequest telephonyNetworkRequest = new TelephonyNetworkRequest(
+                networkRequest, PhoneFactory.getDefaultPhone());
+        if (mNetworkRequestList.remove(telephonyNetworkRequest)) {
+            onEvaluate(REQUESTS_CHANGED, "netReleased");
+            collectReleaseNetworkMetrics(networkRequest);
         }
-        final DcRequest dcRequest =
-                DcRequest.create(networkRequest, createApnRepository(networkRequest));
-        if (dcRequest != null) {
-            if (mPrioritizedDcRequests.remove(dcRequest)) {
-                onEvaluate(REQUESTS_CHANGED, "netReleased");
-                collectReleaseNetworkMetrics(networkRequest);
-                if (VDBG) log("Removed DcRequest, size: " + mPrioritizedDcRequests.size());
-            }
-        }
-    }
-
-    private ApnConfigTypeRepository createApnRepository(NetworkRequest networkRequest) {
-        int phoneIdForRequest = phoneIdForRequest(networkRequest);
-        int subId = mSubscriptionController.getSubIdUsingPhoneId(phoneIdForRequest);
-        CarrierConfigManager configManager = (CarrierConfigManager) mContext
-                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
-
-        PersistableBundle carrierConfig;
-        if (configManager != null) {
-            carrierConfig = configManager.getConfigForSubId(subId);
-        } else {
-            carrierConfig = null;
-        }
-        return new ApnConfigTypeRepository(carrierConfig);
-    }
-
-    private void removeDefaultNetworkChangeCallback() {
-        removeMessages(EVENT_REMOVE_DEFAULT_NETWORK_CHANGE_CALLBACK);
-        mDefaultNetworkCallback.mExpectedSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-        mDefaultNetworkCallback.mSwitchReason =
-                TelephonyEvent.DataSwitch.Reason.DATA_SWITCH_REASON_UNKNOWN;
-        mConnectivityManager.unregisterNetworkCallback(mDefaultNetworkCallback);
     }
 
     private void registerDefaultNetworkChangeCallback(int expectedSubId, int reason) {
         mDefaultNetworkCallback.mExpectedSubId = expectedSubId;
         mDefaultNetworkCallback.mSwitchReason = reason;
-        mConnectivityManager.registerDefaultNetworkCallback(mDefaultNetworkCallback, this);
-        sendMessageDelayed(
-                obtainMessage(EVENT_REMOVE_DEFAULT_NETWORK_CHANGE_CALLBACK),
-                DEFAULT_NETWORK_CHANGE_TIMEOUT_MS);
     }
 
     private void collectRequestNetworkMetrics(NetworkRequest networkRequest) {
@@ -1040,6 +1068,195 @@
         }
     }
 
+    /**
+     * Called when service state changed.
+     */
+    private void onServiceStateChanged(int phoneId) {
+        Phone phone = findPhoneById(phoneId);
+        if (phone != null) {
+            int newRegState = phone.getServiceState()
+                    .getNetworkRegistrationInfo(
+                            NetworkRegistrationInfo.DOMAIN_PS,
+                            AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                    .getRegistrationState();
+            if (newRegState != mPhoneStates[phoneId].dataRegState) {
+                mPhoneStates[phoneId].dataRegState = newRegState;
+                logl("onServiceStateChanged: phoneId:" + phoneId + " dataReg-> "
+                        + NetworkRegistrationInfo.registrationStateToString(newRegState));
+                if (!hasMessages(EVENT_EVALUATE_AUTO_SWITCH)) {
+                    sendEmptyMessage(EVENT_EVALUATE_AUTO_SWITCH);
+                }
+            }
+        }
+    }
+
+    /**
+     * Evaluate if auto switch is suitable at the moment.
+     */
+    private void evaluateIfAutoSwitchIsNeeded() {
+        // auto data switch feature is disabled from server
+        if (mAutoDataSwitchAvailabilityStabilityTimeThreshold < 0) return;
+        // check is valid DSDS
+        if (!isActiveSubId(mPrimaryDataSubId) || mSubscriptionManagerService
+                .getActiveSubIdList(true).length <= 1) {
+            return;
+        }
+
+        Phone primaryDataPhone = getPhoneBySubId(mPrimaryDataSubId);
+        if (primaryDataPhone == null) {
+            loge("evaluateIfAutoSwitchIsNeeded: cannot find primary data phone. subId="
+                    + mPrimaryDataSubId);
+            return;
+        }
+
+        int primaryPhoneId = primaryDataPhone.getPhoneId();
+        log("evaluateIfAutoSwitchIsNeeded: primaryPhoneId: " + primaryPhoneId
+                + " preferredPhoneId: " + mPreferredDataPhoneId);
+        Phone secondaryDataPhone;
+
+        if (mPreferredDataPhoneId == primaryPhoneId) {
+            // on primary data sub
+
+            int candidateSubId = getAutoSwitchTargetSubIdIfExists();
+            if (candidateSubId != INVALID_SUBSCRIPTION_ID) {
+                startAutoDataSwitchStabilityCheck(candidateSubId, mRequirePingTestBeforeDataSwitch);
+            } else {
+                cancelPendingAutoDataSwitch();
+            }
+        } else if ((secondaryDataPhone = findPhoneById(mPreferredDataPhoneId)) != null) {
+            // on secondary data sub
+
+            if (!primaryDataPhone.isUserDataEnabled()
+                    || !secondaryDataPhone.isDataAllowed()) {
+                // immediately switch back if user setting changes
+                mAutoSelectedDataSubId = DEFAULT_SUBSCRIPTION_ID;
+                evaluateIfImmediateDataSwitchIsNeeded("User disabled data settings",
+                        DataSwitch.Reason.DATA_SWITCH_REASON_MANUAL);
+                return;
+            }
+
+            NetworkCapabilities defaultNetworkCapabilities = mConnectivityManager
+                    .getNetworkCapabilities(mConnectivityManager.getActiveNetwork());
+            if (defaultNetworkCapabilities != null && !defaultNetworkCapabilities
+                    .hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
+                log("evaluateIfAutoSwitchIsNeeded: "
+                        + "Default network is active on non-cellular transport");
+                startAutoDataSwitchStabilityCheck(DEFAULT_SUBSCRIPTION_ID, false);
+                return;
+            }
+
+            if (mPhoneStates[secondaryDataPhone.getPhoneId()].dataRegState
+                    != NetworkRegistrationInfo.REGISTRATION_STATE_HOME) {
+                // secondary phone lost its HOME availability
+                startAutoDataSwitchStabilityCheck(DEFAULT_SUBSCRIPTION_ID, false);
+                return;
+            }
+
+            if (isInService(mPhoneStates[primaryPhoneId])) {
+                // primary becomes available
+                startAutoDataSwitchStabilityCheck(DEFAULT_SUBSCRIPTION_ID,
+                        mRequirePingTestBeforeDataSwitch);
+                return;
+            }
+
+            // cancel any previous attempts of switching back to primary
+            cancelPendingAutoDataSwitch();
+        }
+    }
+
+    /**
+     * @param phoneState The phone state to check
+     * @return {@code true} if the phone state is considered in service.
+     */
+    private boolean isInService(@NonNull PhoneState phoneState) {
+        return phoneState.dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_HOME
+                || phoneState.dataRegState == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING;
+    }
+
+    /**
+     * Called when the current environment suits auto data switch.
+     * Start pre-switch validation if the current environment suits auto data switch for
+     * {@link #mAutoDataSwitchAvailabilityStabilityTimeThreshold} MS.
+     * @param targetSubId the target sub Id.
+     * @param needValidation {@code true} if validation is needed.
+     */
+    private void startAutoDataSwitchStabilityCheck(int targetSubId, boolean needValidation) {
+        log("startAutoDataSwitchStabilityCheck: targetSubId=" + targetSubId
+                + " needValidation=" + needValidation);
+        if (!hasMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE, needValidation)) {
+            sendMessageDelayed(obtainMessage(EVENT_MEETS_AUTO_DATA_SWITCH_STATE, targetSubId,
+                            0/*placeholder*/,
+                            needValidation),
+                    mAutoDataSwitchAvailabilityStabilityTimeThreshold);
+        }
+    }
+
+    /**
+     * Cancel any auto switch attempts when the current environment is not suitable for auto switch.
+     */
+    private void cancelPendingAutoDataSwitch() {
+        mAutoSwitchRetryFailedCount = 0;
+        removeMessages(EVENT_MEETS_AUTO_DATA_SWITCH_STATE);
+        if (mValidator.isValidating()) {
+            mValidator.stopValidation();
+
+            removeMessages(EVENT_NETWORK_VALIDATION_DONE);
+            removeMessages(EVENT_NETWORK_AVAILABLE);
+            mPendingSwitchSubId = INVALID_SUBSCRIPTION_ID;
+            mPendingSwitchNeedValidation = false;
+        }
+    }
+
+    /**
+     * Called when consider switching from primary default data sub to another data sub.
+     * @return the target subId if a suitable candidate is found, otherwise return
+     * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}
+     */
+    private int getAutoSwitchTargetSubIdIfExists() {
+        Phone primaryDataPhone = getPhoneBySubId(mPrimaryDataSubId);
+        if (primaryDataPhone == null) {
+            log("getAutoSwitchTargetSubId: no sim loaded");
+            return INVALID_SUBSCRIPTION_ID;
+        }
+
+        int primaryPhoneId = primaryDataPhone.getPhoneId();
+
+        if (!primaryDataPhone.isUserDataEnabled()) {
+            log("getAutoSwitchTargetSubId: user disabled data");
+            return INVALID_SUBSCRIPTION_ID;
+        }
+
+        NetworkCapabilities defaultNetworkCapabilities = mConnectivityManager
+                .getNetworkCapabilities(mConnectivityManager.getActiveNetwork());
+        if (defaultNetworkCapabilities != null && !defaultNetworkCapabilities
+                .hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
+            // Exists other active default transport
+            log("getAutoSwitchTargetSubId: Default network is active on non-cellular transport");
+            return INVALID_SUBSCRIPTION_ID;
+        }
+
+        // check whether primary and secondary signal status worth switching
+        if (isInService(mPhoneStates[primaryPhoneId])) {
+            log("getAutoSwitchTargetSubId: primary is in service");
+            return INVALID_SUBSCRIPTION_ID;
+        }
+        for (int phoneId = 0; phoneId < mPhoneStates.length; phoneId++) {
+            if (phoneId != primaryPhoneId) {
+                // the alternative phone must have HOME availability
+                if (mPhoneStates[phoneId].dataRegState
+                        == NetworkRegistrationInfo.REGISTRATION_STATE_HOME) {
+                    log("getAutoSwitchTargetSubId: found phone " + phoneId + " in HOME service");
+                    Phone secondaryDataPhone = findPhoneById(phoneId);
+                    if (secondaryDataPhone != null && // check auto switch feature enabled
+                            secondaryDataPhone.isDataAllowed()) {
+                        return secondaryDataPhone.getSubId();
+                    }
+                }
+            }
+        }
+        return INVALID_SUBSCRIPTION_ID;
+    }
+
     private TelephonyManager getTm() {
         return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
     }
@@ -1062,11 +1279,12 @@
         boolean diffDetected = mHalCommandToUse != HAL_COMMAND_PREFERRED_DATA && requestsChanged;
 
         // Check if user setting of default non-opportunistic data sub is changed.
-        final int primaryDataSubId = mSubscriptionController.getDefaultDataSubId();
+        int primaryDataSubId = mSubscriptionManagerService.getDefaultDataSubId();
         if (primaryDataSubId != mPrimaryDataSubId) {
             sb.append(" mPrimaryDataSubId ").append(mPrimaryDataSubId).append("->")
                 .append(primaryDataSubId);
             mPrimaryDataSubId = primaryDataSubId;
+            mLastSwitchPreferredDataReason = DataSwitch.Reason.DATA_SWITCH_REASON_MANUAL;
         }
 
         // Check to see if there is any active subscription on any phone
@@ -1074,14 +1292,21 @@
 
         // Check if phoneId to subId mapping is changed.
         for (int i = 0; i < mActiveModemCount; i++) {
-            int sub = mSubscriptionController.getSubIdUsingPhoneId(i);
+            int sub = SubscriptionManager.getSubscriptionId(i);
 
             if (SubscriptionManager.isValidSubscriptionId(sub)) hasAnyActiveSubscription = true;
 
             if (sub != mPhoneSubscriptions[i]) {
                 sb.append(" phone[").append(i).append("] ").append(mPhoneSubscriptions[i]);
                 sb.append("->").append(sub);
+                if (mAutoSelectedDataSubId == mPhoneSubscriptions[i]) {
+                    mAutoSelectedDataSubId = DEFAULT_SUBSCRIPTION_ID;
+                }
                 mPhoneSubscriptions[i] = sub;
+                // Listen to IMS radio tech change for new sub
+                if (SubscriptionManager.isValidSubscriptionId(sub)) {
+                    registerForImsRadioTechChange(mContext, i);
+                }
                 diffDetected = true;
             }
         }
@@ -1106,11 +1331,11 @@
         if (hasAnyActiveSubscription) updatePreferredDataPhoneId();
 
         if (oldPreferredDataPhoneId != mPreferredDataPhoneId) {
-            sb.append(" preferred phoneId ").append(oldPreferredDataPhoneId)
+            sb.append(" preferred data phoneId ").append(oldPreferredDataPhoneId)
                     .append("->").append(mPreferredDataPhoneId);
             diffDetected = true;
         } else if (oldPreferredDataSubId != mPreferredDataSubId.get()) {
-            log("SIM refresh, notify dds change");
+            logl("SIM refresh, notify dds change");
             // Inform connectivity about the active data phone
             notifyPreferredDataSubIdChanged();
         }
@@ -1119,7 +1344,7 @@
         // DDS are out of sync after APM, AP should force DDS when radio on. long term solution
         // should be having API to query preferred data modem to detect the out-of-sync scenarios.
         if (diffDetected || EVALUATION_REASON_RADIO_ON.equals(reason)) {
-            log("evaluating due to " + sb.toString());
+            logl("evaluating due to " + sb);
             if (mHalCommandToUse == HAL_COMMAND_PREFERRED_DATA) {
                 // With HAL_COMMAND_PREFERRED_DATA, all phones are assumed to allow PS attach.
                 // So marking all phone as active, and the phone with mPreferredDataPhoneId
@@ -1147,27 +1372,17 @@
                     }
 
                     if (newActivePhones.size() < mMaxDataAttachModemCount) {
-                        if (PhoneFactory.getDefaultPhone().isUsingNewDataStack()) {
-                            for (TelephonyNetworkRequest networkRequest : mNetworkRequestList) {
-                                int phoneIdForRequest = phoneIdForRequest(networkRequest);
-                                if (phoneIdForRequest == INVALID_PHONE_INDEX) continue;
-                                if (newActivePhones.contains(phoneIdForRequest)) continue;
-                                newActivePhones.add(phoneIdForRequest);
-                                if (newActivePhones.size() >= mMaxDataAttachModemCount) break;
-                            }
-                        } else {
-                            for (DcRequest dcRequest : mPrioritizedDcRequests) {
-                                int phoneIdForRequest = phoneIdForRequest(dcRequest.networkRequest);
-                                if (phoneIdForRequest == INVALID_PHONE_INDEX) continue;
-                                if (newActivePhones.contains(phoneIdForRequest)) continue;
-                                newActivePhones.add(phoneIdForRequest);
-                                if (newActivePhones.size() >= mMaxDataAttachModemCount) break;
-                            }
+                        for (TelephonyNetworkRequest networkRequest : mNetworkRequestList) {
+                            int phoneIdForRequest = phoneIdForRequest(networkRequest);
+                            if (phoneIdForRequest == INVALID_PHONE_INDEX) continue;
+                            if (newActivePhones.contains(phoneIdForRequest)) continue;
+                            newActivePhones.add(phoneIdForRequest);
+                            if (newActivePhones.size() >= mMaxDataAttachModemCount) break;
                         }
                     }
 
                     if (newActivePhones.size() < mMaxDataAttachModemCount
-                            && newActivePhones.contains(mPreferredDataPhoneId)
+                            && !newActivePhones.contains(mPreferredDataPhoneId)
                             && SubscriptionManager.isUsableSubIdValue(mPreferredDataPhoneId)) {
                         newActivePhones.add(mPreferredDataPhoneId);
                     }
@@ -1175,7 +1390,7 @@
 
                 if (VDBG) {
                     log("mPrimaryDataSubId = " + mPrimaryDataSubId);
-                    log("mOpptDataSubId = " + mOpptDataSubId);
+                    log("mAutoSelectedDataSubId = " + mAutoSelectedDataSubId);
                     for (int i = 0; i < mActiveModemCount; i++) {
                         log(" phone[" + i + "] using sub[" + mPhoneSubscriptions[i] + "]");
                     }
@@ -1200,6 +1415,8 @@
 
     protected static class PhoneState {
         public volatile boolean active = false;
+        public @NetworkRegistrationInfo.RegistrationState int dataRegState =
+                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING;
         public long lastRequested = 0;
     }
 
@@ -1217,7 +1434,7 @@
         PhoneState state = mPhoneStates[phoneId];
         if (state.active == active) return;
         state.active = active;
-        log((active ? "activate " : "deactivate ") + phoneId);
+        logl((active ? "activate " : "deactivate ") + phoneId);
         state.lastRequested = System.currentTimeMillis();
         sendRilCommands(phoneId);
     }
@@ -1264,7 +1481,7 @@
 
     protected void sendRilCommands(int phoneId) {
         if (!SubscriptionManager.isValidPhoneId(phoneId)) {
-            log("sendRilCommands: skip dds switch due to invalid phoneId=" + phoneId);
+            logl("sendRilCommands: skip dds switch due to invalid phoneId=" + phoneId);
             return;
         }
 
@@ -1276,7 +1493,7 @@
             }
         } else if (phoneId == mPreferredDataPhoneId) {
             // Only setPreferredDataModem if the phoneId equals to current mPreferredDataPhoneId
-            log("sendRilCommands: setPreferredDataModem - phoneId: " + phoneId);
+            logl("sendRilCommands: setPreferredDataModem - phoneId: " + phoneId);
             mRadioConfig.setPreferredDataModem(mPreferredDataPhoneId, message);
         }
     }
@@ -1286,18 +1503,13 @@
                 .getNumberOfModemsWithSimultaneousDataConnections();
         if (mMaxDataAttachModemCount != newMaxDataAttachModemCount) {
             mMaxDataAttachModemCount = newMaxDataAttachModemCount;
-            log("Max active phones changed to " + mMaxDataAttachModemCount);
+            logl("Max active phones changed to " + mMaxDataAttachModemCount);
             onEvaluate(REQUESTS_UNCHANGED, "phoneCfgChanged");
         }
     }
 
-    // Merge phoneIdForRequest(NetworkRequest netRequest) after Phone.isUsingNewDataStack() is
-    // cleaned up.
     private int phoneIdForRequest(TelephonyNetworkRequest networkRequest) {
-        return phoneIdForRequest(networkRequest.getNativeNetworkRequest());
-    }
-
-    private int phoneIdForRequest(NetworkRequest netRequest) {
+        NetworkRequest netRequest = networkRequest.getNativeNetworkRequest();
         int subId = getSubIdFromNetworkSpecifier(netRequest.getNetworkSpecifier());
 
         if (subId == DEFAULT_SUBSCRIPTION_ID) return mPreferredDataPhoneId;
@@ -1341,71 +1553,82 @@
         return INVALID_SUBSCRIPTION_ID;
     }
 
-    private int getSubIdForDefaultNetworkRequests() {
-        if (mSubscriptionController.isActiveSubId(mOpptDataSubId)) {
-            return mOpptDataSubId;
-        } else {
-            return mPrimaryDataSubId;
-        }
+    private boolean isActiveSubId(int subId) {
+        SubscriptionInfoInternal subInfo = mSubscriptionManagerService
+                .getSubscriptionInfoInternal(subId);
+        return subInfo != null && subInfo.isActive();
     }
 
     // This updates mPreferredDataPhoneId which decides which phone should handle default network
     // requests.
     protected void updatePreferredDataPhoneId() {
-        Phone voicePhone = findPhoneById(mPhoneIdInVoiceCall);
-        boolean isDataEnabled = false;
-        if (voicePhone != null) {
-            if (voicePhone.isUsingNewDataStack()) {
-                isDataEnabled = voicePhone.getDataSettingsManager()
-                        .isDataEnabled(ApnSetting.TYPE_DEFAULT);
-            } else {
-                isDataEnabled = voicePhone.getDataEnabledSettings()
-                        .isDataEnabled(ApnSetting.TYPE_DEFAULT);
-            }
-        }
-
         if (mEmergencyOverride != null && findPhoneById(mEmergencyOverride.mPhoneId) != null) {
             // Override DDS for emergency even if user data is not enabled, since it is an
             // emergency.
             // TODO: Provide a notification to the user that metered data is currently being
             // used during this period.
-            log("updatePreferredDataPhoneId: preferred data overridden for emergency."
+            logl("updatePreferredDataPhoneId: preferred data overridden for emergency."
                     + " phoneId = " + mEmergencyOverride.mPhoneId);
             mPreferredDataPhoneId = mEmergencyOverride.mPhoneId;
-        } else if (isDataEnabled) {
-            // If a phone is in call and user enabled its mobile data, we
-            // should switch internet connection to it. Because the other modem
-            // will lose data connection anyway.
-            // TODO: validate network first.
-            mPreferredDataPhoneId = mPhoneIdInVoiceCall;
+            mLastSwitchPreferredDataReason = DataSwitch.Reason.DATA_SWITCH_REASON_UNKNOWN;
         } else {
-            int subId = getSubIdForDefaultNetworkRequests();
-            int phoneId = SubscriptionManager.INVALID_PHONE_INDEX;
-
-            if (SubscriptionManager.isUsableSubIdValue(subId)) {
-                for (int i = 0; i < mActiveModemCount; i++) {
-                    if (mPhoneSubscriptions[i] == subId) {
-                        phoneId = i;
-                        break;
-                    }
+            int imsRegTech = mImsRegTechProvider.get(mContext, mPhoneIdInVoiceCall);
+            if (isAnyVoiceCallActiveOnDevice() && imsRegTech != REGISTRATION_TECH_IWLAN) {
+                if (imsRegTech != REGISTRATION_TECH_CROSS_SIM) {
+                    mPreferredDataPhoneId = shouldSwitchDataDueToInCall()
+                            ? mPhoneIdInVoiceCall : getFallbackDataPhoneIdForInternetRequests();
+                } else {
+                    logl("IMS call on cross-SIM, skip switching data to phone "
+                            + mPhoneIdInVoiceCall);
                 }
+            } else {
+                mPreferredDataPhoneId = getFallbackDataPhoneIdForInternetRequests();
             }
-
-            mPreferredDataPhoneId = phoneId;
         }
 
-        mPreferredDataSubId.set(
-                mSubscriptionController.getSubIdUsingPhoneId(mPreferredDataPhoneId));
+        mPreferredDataSubId.set(SubscriptionManager.getSubscriptionId(mPreferredDataPhoneId));
+    }
+
+    /**
+     * @return the default data phone Id (or auto selected phone Id in auto data switch/CBRS case)
+     */
+    private int getFallbackDataPhoneIdForInternetRequests() {
+        int fallbackSubId = isActiveSubId(mAutoSelectedDataSubId)
+                ? mAutoSelectedDataSubId : mPrimaryDataSubId;
+
+        if (SubscriptionManager.isUsableSubIdValue(fallbackSubId)) {
+            for (int phoneId = 0; phoneId < mActiveModemCount; phoneId++) {
+                if (mPhoneSubscriptions[phoneId] == fallbackSubId) {
+                    return phoneId;
+                }
+            }
+        }
+        return SubscriptionManager.INVALID_PHONE_INDEX;
+    }
+
+    /**
+     * If a phone is in call and user enabled its mobile data and auto data switch feature, we
+     * should switch internet connection to it because the other modem will lose data connection
+     * anyway.
+     * @return {@code true} if should switch data to the phone in voice call
+     */
+    private boolean shouldSwitchDataDueToInCall() {
+        Phone voicePhone = findPhoneById(mPhoneIdInVoiceCall);
+        Phone defaultDataPhone = getPhoneBySubId(mPrimaryDataSubId);
+        return defaultDataPhone != null // check user enabled data
+                && defaultDataPhone.isUserDataEnabled()
+                && voicePhone != null // check user enabled voice during call feature
+                && voicePhone.isDataAllowed();
     }
 
     protected void transitionToEmergencyPhone() {
         if (mActiveModemCount <= 0) {
-            log("No phones: unable to reset preferred phone for emergency");
+            logl("No phones: unable to reset preferred phone for emergency");
             return;
         }
 
         if (mPreferredDataPhoneId != DEFAULT_EMERGENCY_PHONE_ID) {
-            log("No active subscriptions: resetting preferred phone to 0 for emergency");
+            logl("No active subscriptions: resetting preferred phone to 0 for emergency");
             mPreferredDataPhoneId = DEFAULT_EMERGENCY_PHONE_ID;
         }
 
@@ -1415,6 +1638,10 @@
         }
     }
 
+    private Phone getPhoneBySubId(int subId) {
+        return findPhoneById(mSubscriptionManagerService.getPhoneId(subId));
+    }
+
     private Phone findPhoneById(final int phoneId) {
         if (!SubscriptionManager.isValidPhoneId(phoneId)) {
             return null;
@@ -1426,15 +1653,16 @@
             TelephonyNetworkRequest networkRequest, int phoneId) {
         if (!SubscriptionManager.isValidPhoneId(phoneId)) return false;
 
+        int subId = SubscriptionManager.getSubscriptionId(phoneId);
+
         // In any case, if phone state is inactive, don't apply the network request.
-        if (!isPhoneActive(phoneId) || (
-                mSubscriptionController.getSubIdUsingPhoneId(phoneId) == INVALID_SUBSCRIPTION_ID
+        if (!isPhoneActive(phoneId) || (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID
                 && !isEmergencyNetworkRequest(networkRequest))) {
             return false;
         }
 
         NetworkRequest netRequest = networkRequest.getNativeNetworkRequest();
-        int subId = getSubIdFromNetworkSpecifier(netRequest.getNetworkSpecifier());
+        subId = getSubIdFromNetworkSpecifier(netRequest.getNetworkSpecifier());
 
         //if this phone is an emergency networkRequest
         //and subId is not specified that is invalid or default
@@ -1488,45 +1716,67 @@
      */
     private void setOpportunisticDataSubscription(int subId, boolean needValidation,
             ISetOpportunisticDataCallback callback) {
-        if (!mSubscriptionController.isActiveSubId(subId)
-                && subId != SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
-            log("Can't switch data to inactive subId " + subId);
+        validate(subId, needValidation,
+                DataSwitch.Reason.DATA_SWITCH_REASON_CBRS, callback);
+    }
+
+    /**
+     * Try setup a new internet connection on the subId that's pending validation. If the validation
+     * succeeds, this subId will be evaluated for being the preferred data subId; If fails, nothing
+     * happens.
+     * Callback will be updated with the validation result.
+     *
+     * @param subId Sub Id that's pending switch, awaiting validation.
+     * @param needValidation {@code false} if switch to the subId even if validation fails.
+     * @param switchReason The switch reason for this validation
+     * @param callback Optional - specific for external opportunistic sub validation request.
+     */
+    private void validate(int subId, boolean needValidation, int switchReason,
+            @Nullable ISetOpportunisticDataCallback callback) {
+        logl("Validate subId " + subId + " due to " + switchReasonToString(switchReason)
+                + " needValidation=" + needValidation);
+        int subIdToValidate = (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)
+                ? mPrimaryDataSubId : subId;
+        if (!isActiveSubId(subIdToValidate)) {
+            logl("Can't switch data to inactive subId " + subIdToValidate);
+            if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
+                // the default data sub is not selected yet, store the intent of switching to
+                // default subId once it becomes available.
+                mAutoSelectedDataSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+            }
             sendSetOpptCallbackHelper(callback, SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION);
             return;
         }
 
-        // Remove EVENT_NETWORK_VALIDATION_DONE. Don't handle validation result of previously subId
-        // if queued.
-        removeMessages(EVENT_NETWORK_VALIDATION_DONE);
-        removeMessages(EVENT_NETWORK_AVAILABLE);
-
-        int subIdToValidate = (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)
-                ? mPrimaryDataSubId : subId;
-
-        mPendingSwitchSubId = INVALID_SUBSCRIPTION_ID;
-
         if (mValidator.isValidating()) {
             mValidator.stopValidation();
             sendSetOpptCallbackHelper(mSetOpptSubCallback, SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED);
             mSetOpptSubCallback = null;
         }
 
-        if (subId == mOpptDataSubId) {
+        // Remove EVENT_NETWORK_VALIDATION_DONE. Don't handle validation result of previous subId
+        // if queued.
+        removeMessages(EVENT_NETWORK_VALIDATION_DONE);
+        removeMessages(EVENT_NETWORK_AVAILABLE);
+
+        mPendingSwitchSubId = INVALID_SUBSCRIPTION_ID;
+
+        if (subIdToValidate == mPreferredDataSubId.get()) {
             sendSetOpptCallbackHelper(callback, SET_OPPORTUNISTIC_SUB_SUCCESS);
             return;
         }
 
-        logDataSwitchEvent(subId == DEFAULT_SUBSCRIPTION_ID ? mPrimaryDataSubId : subId,
+        mLastSwitchPreferredDataReason = switchReason;
+        logDataSwitchEvent(subIdToValidate,
                 TelephonyEvent.EventState.EVENT_STATE_START,
-                DataSwitch.Reason.DATA_SWITCH_REASON_CBRS);
-        registerDefaultNetworkChangeCallback(
-                subId == DEFAULT_SUBSCRIPTION_ID ? mPrimaryDataSubId : subId,
-                DataSwitch.Reason.DATA_SWITCH_REASON_CBRS);
+                switchReason);
+        registerDefaultNetworkChangeCallback(subIdToValidate,
+                switchReason);
 
         // If validation feature is not supported, set it directly. Otherwise,
         // start validation on the subscription first.
         if (!mValidator.isValidationFeatureSupported()) {
-            setOpportunisticSubscriptionInternal(subId);
+            setAutoSelectedDataSubIdInternal(subIdToValidate);
             sendSetOpptCallbackHelper(callback, SET_OPPORTUNISTIC_SUB_SUCCESS);
             return;
         }
@@ -1563,36 +1813,46 @@
         try {
             callback.onComplete(result);
         } catch (RemoteException exception) {
-            log("RemoteException " + exception);
+            logl("RemoteException " + exception);
         }
     }
 
     /**
-     * Set opportunistic data subscription.
+     * Evaluate whether the specified sub Id can be set to be the preferred data sub Id.
+     *
+     * @param subId The subId that we tried to validate: could possibly be unvalidated if validation
+     * feature is not supported.
      */
-    private void setOpportunisticSubscriptionInternal(int subId) {
-        if (mOpptDataSubId != subId) {
-            mOpptDataSubId = subId;
-            onEvaluate(REQUESTS_UNCHANGED, "oppt data subId changed");
+    private void setAutoSelectedDataSubIdInternal(int subId) {
+        if (mAutoSelectedDataSubId != subId) {
+            mAutoSelectedDataSubId = subId;
+            onEvaluate(REQUESTS_UNCHANGED, switchReasonToString(mLastSwitchPreferredDataReason));
         }
     }
 
     private void confirmSwitch(int subId, boolean confirm) {
-        log("confirmSwitch: subId " + subId + (confirm ? " confirmed." : " cancelled."));
+        logl("confirmSwitch: subId " + subId + (confirm ? " confirmed." : " cancelled."));
         int resultForCallBack;
-        if (!mSubscriptionController.isActiveSubId(subId)) {
-            log("confirmSwitch: subId " + subId + " is no longer active");
+        if (!isActiveSubId(subId)) {
+            logl("confirmSwitch: subId " + subId + " is no longer active");
             resultForCallBack = SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION;
+            mAutoSwitchRetryFailedCount = 0;
         } else if (!confirm) {
             resultForCallBack = SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED;
+
+            // retry for auto data switch validation failure
+            if (mLastSwitchPreferredDataReason == DataSwitch.Reason.DATA_SWITCH_REASON_AUTO) {
+                scheduleAutoSwitchRetryEvaluation();
+                mAutoSwitchRetryFailedCount++;
+            }
         } else {
-            if (mSubscriptionController.isOpportunistic(subId)) {
-                setOpportunisticSubscriptionInternal(subId);
+            if (subId == mPrimaryDataSubId) {
+                setAutoSelectedDataSubIdInternal(DEFAULT_SUBSCRIPTION_ID);
             } else {
-                // Switching data back to primary subscription.
-                setOpportunisticSubscriptionInternal(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
+                setAutoSelectedDataSubIdInternal(subId);
             }
             resultForCallBack = SET_OPPORTUNISTIC_SUB_SUCCESS;
+            mAutoSwitchRetryFailedCount = 0;
         }
 
         // Trigger callback if needed
@@ -1601,6 +1861,23 @@
         mPendingSwitchSubId = INVALID_SUBSCRIPTION_ID;
     }
 
+    /**
+     * Schedule auto data switch evaluation retry if haven't reached the max retry count.
+     */
+    private void scheduleAutoSwitchRetryEvaluation() {
+        if (mAutoSwitchRetryFailedCount < mAutoDataSwitchValidationMaxRetry) {
+            if (!hasMessages(EVENT_EVALUATE_AUTO_SWITCH)) {
+                sendMessageDelayed(obtainMessage(EVENT_EVALUATE_AUTO_SWITCH),
+                        mAutoDataSwitchAvailabilityStabilityTimeThreshold
+                                << mAutoSwitchRetryFailedCount);
+            }
+        } else {
+            logl("scheduleAutoSwitchEvaluation: reached max auto switch retry count "
+                    + mAutoDataSwitchValidationMaxRetry);
+            mAutoSwitchRetryFailedCount = 0;
+        }
+    }
+
     private void onNetworkAvailable(int subId, Network network) {
         log("onNetworkAvailable: on subId " + subId);
         // Do nothing unless pending switch matches target subId and it doesn't require
@@ -1613,7 +1890,7 @@
     }
 
     private void onValidationDone(int subId, boolean passed) {
-        log("onValidationDone: " + (passed ? "passed" : "failed") + " on subId " + subId);
+        logl("onValidationDone: " + (passed ? "passed" : "failed") + " on subId " + subId);
         if (mPendingSwitchSubId == INVALID_SUBSCRIPTION_ID || mPendingSwitchSubId != subId) return;
 
         // If validation failed and mPendingSwitch.mNeedValidation is false, we still confirm
@@ -1639,7 +1916,7 @@
      */
     public void trySetOpportunisticDataSubscription(int subId, boolean needValidation,
             ISetOpportunisticDataCallback callback) {
-        log("Try set opportunistic data subscription to subId " + subId
+        logl("Try set opportunistic data subscription to subId " + subId
                 + (needValidation ? " with " : " without ") + "validation");
         PhoneSwitcher.this.obtainMessage(EVENT_OPPT_DATA_SUB_CHANGED,
                 subId, needValidation ? 1 : 0, callback).sendToTarget();
@@ -1660,21 +1937,38 @@
                 ? HAL_COMMAND_PREFERRED_DATA : HAL_COMMAND_ALLOW_DATA;
     }
 
-    public int getOpportunisticDataSubscriptionId() {
-        return mOpptDataSubId;
-    }
-
     public int getPreferredDataPhoneId() {
         return mPreferredDataPhoneId;
     }
 
+    /**
+     * Log debug messages and also log into the local log.
+     * @param l debug messages
+     */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    protected void log(String l) {
-        Rlog.d(LOG_TAG, l);
+    protected void logl(String l) {
+        log(l);
         mLocalLog.log(l);
     }
 
     /**
+     * Log debug messages.
+     * @param s debug messages
+     */
+    private void log(@NonNull String s) {
+        Rlog.d(LOG_TAG, s);
+    }
+
+    /**
+     * Log debug error messages.
+     * @param s debug messages
+     */
+    private void loge(@NonNull String s) {
+        Rlog.e(LOG_TAG, s);
+    }
+
+
+    /**
      * Convert data switch reason into string.
      *
      * @param reason The switch reason.
@@ -1690,6 +1984,8 @@
                 return "IN_CALL";
             case TelephonyEvent.DataSwitch.Reason.DATA_SWITCH_REASON_CBRS:
                 return "CBRS";
+            case TelephonyEvent.DataSwitch.Reason.DATA_SWITCH_REASON_AUTO:
+                return "AUTO";
             default: return "UNKNOWN(" + reason + ")";
         }
     }
@@ -1720,8 +2016,8 @@
      * @param reason The switching reason.
      */
     private void logDataSwitchEvent(int subId, int state, int reason) {
-        log("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;
@@ -1734,7 +2030,7 @@
     protected void notifyPreferredDataSubIdChanged() {
         TelephonyRegistryManager telephonyRegistryManager = (TelephonyRegistryManager) mContext
                 .getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
-        log("notifyPreferredDataSubIdChanged to " + mPreferredDataSubId.get());
+        logl("notifyPreferredDataSubIdChanged to " + mPreferredDataSubId.get());
         telephonyRegistryManager.notifyActiveDataSubIdChanged(mPreferredDataSubId.get());
     }
 
@@ -1745,6 +2041,13 @@
         return mPreferredDataSubId.get();
     }
 
+    /**
+     * @return The auto selected data subscription id.
+     */
+    public int getAutoSelectedDataSubId() {
+        return mAutoSelectedDataSubId;
+    }
+
     // TODO (b/148396668): add an internal callback method to monitor phone capability change,
     // and hook this call to that callback.
     private void onPhoneCapabilityChanged(PhoneCapability capability) {
@@ -1759,23 +2062,32 @@
         for (int i = 0; i < mActiveModemCount; i++) {
             PhoneState ps = mPhoneStates[i];
             c.setTimeInMillis(ps.lastRequested);
-            pw.println("PhoneId(" + i + ") active=" + ps.active + ", lastRequest=" +
-                    (ps.lastRequested == 0 ? "never" :
+            pw.println("PhoneId(" + i + ") active=" + ps.active + ", dataRegState="
+                    + NetworkRegistrationInfo.registrationStateToString(ps.dataRegState)
+                    + ", lastRequest="
+                    + (ps.lastRequested == 0 ? "never" :
                      String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c)));
         }
         pw.println("mPreferredDataPhoneId=" + mPreferredDataPhoneId);
         pw.println("mPreferredDataSubId=" + mPreferredDataSubId.get());
-        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("mOpptDataSubId=" + mOpptDataSubId);
+        pw.println("mAutoSelectedDataSubId=" + mAutoSelectedDataSubId);
         pw.println("mIsRegisteredForImsRadioTechChange=" + mIsRegisteredForImsRadioTechChange);
         pw.println("mPendingSwitchNeedValidation=" + mPendingSwitchNeedValidation);
         pw.println("mMaxDataAttachModemCount=" + mMaxDataAttachModemCount);
         pw.println("mActiveModemCount=" + mActiveModemCount);
         pw.println("mPhoneIdInVoiceCall=" + mPhoneIdInVoiceCall);
         pw.println("mCurrentDdsSwitchFailure=" + mCurrentDdsSwitchFailure);
+        pw.println("mAutoDataSwitchAvailabilityStabilityTimeThreshold="
+                + mAutoDataSwitchAvailabilityStabilityTimeThreshold);
+        pw.println("mAutoDataSwitchValidationMaxRetry=" + mAutoDataSwitchValidationMaxRetry);
+        pw.println("mRequirePingTestBeforeDataSwitch=" + mRequirePingTestBeforeDataSwitch);
+        pw.println("mLastSwitchPreferredDataReason="
+                + switchReasonToString(mLastSwitchPreferredDataReason));
+        pw.println("mDisplayedAutoSwitchNotification=" + mDisplayedAutoSwitchNotification);
         pw.println("Local logs:");
         pw.increaseIndent();
         mLocalLog.dump(fd, pw, args);
@@ -1793,64 +2105,109 @@
         boolean commandSuccess = ar != null && ar.exception == null;
         int phoneId = (int) ar.userObj;
         if (mEmergencyOverride != null) {
-            log("Emergency override result sent = " + commandSuccess);
+            logl("Emergency override result sent = " + commandSuccess);
             mEmergencyOverride.sendOverrideCompleteCallbackResultAndClear(commandSuccess);
             // Do not retry , as we do not allow changes in onEvaluate during an emergency
             // call. When the call ends, we will start the countdown to remove the override.
         } else if (!commandSuccess) {
-            log("onDdsSwitchResponse: DDS switch failed. with exception " + ar.exception);
+            logl("onDdsSwitchResponse: DDS switch failed. with exception " + ar.exception);
             if (ar.exception instanceof CommandException) {
                 CommandException.Error error = ((CommandException)
                         (ar.exception)).getCommandError();
                 mCurrentDdsSwitchFailure.get(phoneId).add(error);
                 if (error == CommandException.Error.OP_NOT_ALLOWED_DURING_VOICE_CALL) {
-                    log("onDdsSwitchResponse: Wait for call end indication");
+                    logl("onDdsSwitchResponse: Wait for call end indication");
                     return;
                 } else if (error == CommandException.Error.INVALID_SIM_STATE) {
                     /* If there is a attach failure due to sim not ready then
                     hold the retry until sim gets ready */
-                    log("onDdsSwitchResponse: Wait for SIM to get READY");
+                    logl("onDdsSwitchResponse: Wait for SIM to get READY");
                     return;
                 }
             }
-            log("onDdsSwitchResponse: Scheduling DDS switch retry");
+            logl("onDdsSwitchResponse: Scheduling DDS switch retry");
             sendMessageDelayed(Message.obtain(this, EVENT_MODEM_COMMAND_RETRY,
                         phoneId), MODEM_COMMAND_RETRY_PERIOD_MS);
             return;
         }
-        if (commandSuccess) log("onDdsSwitchResponse: DDS switch success on phoneId = " + phoneId);
+        if (commandSuccess) logl("onDdsSwitchResponse: DDS switch success on phoneId = " + phoneId);
         mCurrentDdsSwitchFailure.get(phoneId).clear();
         // Notify all registrants
         mActivePhoneRegistrants.notifyRegistrants();
         notifyPreferredDataSubIdChanged();
+        displayAutoDataSwitchNotification();
+    }
+
+    /**
+     * Display a notification the first time auto data switch occurs.
+     */
+    private void displayAutoDataSwitchNotification() {
+        NotificationManager notificationManager = (NotificationManager)
+                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+
+        if (mDisplayedAutoSwitchNotification) {
+            // cancel posted notification if any exist
+            log("displayAutoDataSwitchNotification: canceling any notifications for subId "
+                    + mAutoSelectedDataSubId);
+            notificationManager.cancel(AUTO_DATA_SWITCH_NOTIFICATION_TAG,
+                    AUTO_DATA_SWITCH_NOTIFICATION_ID);
+            return;
+        }
+        // proceed only the first time auto data switch occurs, which includes data during call
+        if (mLastSwitchPreferredDataReason != DataSwitch.Reason.DATA_SWITCH_REASON_AUTO) {
+            log("displayAutoDataSwitchNotification: Ignore DDS switch due to "
+                    + switchReasonToString(mLastSwitchPreferredDataReason));
+            return;
+        }
+        SubscriptionInfo subInfo = mSubscriptionManagerService
+                .getSubscriptionInfo(mAutoSelectedDataSubId);
+        if (subInfo == null || subInfo.isOpportunistic()) {
+            loge("displayAutoDataSwitchNotification: mAutoSelectedDataSubId="
+                    + mAutoSelectedDataSubId + " unexpected subInfo " + subInfo);
+            return;
+        }
+        logl("displayAutoDataSwitchNotification: display for subId=" + mAutoSelectedDataSubId);
+        // "Mobile network settings" screen / dialog
+        Intent intent = new Intent(Settings.ACTION_NETWORK_OPERATOR_SETTINGS);
+        final Bundle fragmentArgs = new Bundle();
+        // Special contract for Settings to highlight permission row
+        fragmentArgs.putString(SETTINGS_EXTRA_FRAGMENT_ARG_KEY, AUTO_DATA_SWITCH_SETTING_R_ID);
+        intent.putExtra(Settings.EXTRA_SUB_ID, mAutoSelectedDataSubId);
+        intent.putExtra(SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs);
+        PendingIntent contentIntent = PendingIntent.getActivity(
+                mContext, mAutoSelectedDataSubId, intent, PendingIntent.FLAG_IMMUTABLE);
+
+        CharSequence activeCarrierName = subInfo.getDisplayName();
+        CharSequence contentTitle = mContext.getString(
+                com.android.internal.R.string.auto_data_switch_title, activeCarrierName);
+        CharSequence contentText = mContext.getText(
+                com.android.internal.R.string.auto_data_switch_content);
+
+        final Notification notif = new Notification.Builder(mContext)
+                .setContentTitle(contentTitle)
+                .setContentText(contentText)
+                .setSmallIcon(android.R.drawable.stat_sys_warning)
+                .setColor(mContext.getResources().getColor(
+                        com.android.internal.R.color.system_notification_accent_color))
+                .setChannelId(NotificationChannelController.CHANNEL_ID_MOBILE_DATA_STATUS)
+                .setContentIntent(contentIntent)
+                .setStyle(new Notification.BigTextStyle().bigText(contentText))
+                .build();
+        notificationManager.notify(AUTO_DATA_SWITCH_NOTIFICATION_TAG,
+                AUTO_DATA_SWITCH_NOTIFICATION_ID, notif);
+        mDisplayedAutoSwitchNotification = true;
     }
 
     private boolean isPhoneIdValidForRetry(int phoneId) {
-        int phoneIdForRequest = INVALID_PHONE_INDEX;
-        int ddsPhoneId = mSubscriptionController.getPhoneId(
-                mSubscriptionController.getDefaultDataSubId());
+        int ddsPhoneId = mSubscriptionManagerService.getPhoneId(
+                mSubscriptionManagerService.getDefaultDataSubId());
         if (ddsPhoneId != INVALID_PHONE_INDEX && ddsPhoneId == phoneId) {
             return true;
         } else {
-            if (PhoneFactory.getDefaultPhone().isUsingNewDataStack()) {
-                if (mNetworkRequestList.isEmpty()) return false;
-                for (TelephonyNetworkRequest networkRequest : mNetworkRequestList) {
-                    phoneIdForRequest = phoneIdForRequest(networkRequest);
-                    if (phoneIdForRequest == phoneId) {
-                        return true;
-                    }
-                }
-            } else {
-                if (mPrioritizedDcRequests.size() == 0) {
-                    return false;
-                }
-                for (DcRequest dcRequest : mPrioritizedDcRequests) {
-                    if (dcRequest != null) {
-                        phoneIdForRequest = phoneIdForRequest(dcRequest.networkRequest);
-                        if (phoneIdForRequest == phoneId) {
-                            return true;
-                        }
-                    }
+            if (mNetworkRequestList.isEmpty()) return false;
+            for (TelephonyNetworkRequest networkRequest : mNetworkRequestList) {
+                if (phoneIdForRequest(networkRequest) == phoneId) {
+                    return true;
                 }
             }
         }
diff --git a/src/java/com/android/internal/telephony/data/QosCallbackTracker.java b/src/java/com/android/internal/telephony/data/QosCallbackTracker.java
index b7cecbd..ac04627 100644
--- a/src/java/com/android/internal/telephony/data/QosCallbackTracker.java
+++ b/src/java/com/android/internal/telephony/data/QosCallbackTracker.java
@@ -37,6 +37,7 @@
 
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -53,8 +54,7 @@
     private static final int DEDICATED_BEARER_EVENT_STATE_DELETED = 3;
 
     private final @NonNull String mLogTag;
-    // TODO: Change this to TelephonyNetworkAgent
-    private final @NonNull NotifyQosSessionInterface mNetworkAgent;
+    private final @NonNull TelephonyNetworkAgent mNetworkAgent;
     private final @NonNull Map<Integer, QosBearerSession> mQosBearerSessions;
     private final @NonNull RcsStats mRcsStats;
 
@@ -80,12 +80,20 @@
         /**
          * Filter using the remote address.
          *
-         * @param address The local address.
+         * @param address The remote address.
          * @param startPort Starting port.
          * @param endPort Ending port.
          * @return {@code true} if matches, {@code false} otherwise.
          */
         boolean matchesRemoteAddress(InetAddress address, int startPort, int endPort);
+
+        /**
+         * Filter using the protocol
+         *
+         * @param protocol ID
+         * @return {@code true} if matches, {@code false} otherwise.
+         */
+        boolean matchesProtocol(int protocol);
     }
 
     /**
@@ -94,8 +102,7 @@
      * @param networkAgent The network agent to send events to.
      * @param phone The phone instance.
      */
-    public QosCallbackTracker(@NonNull NotifyQosSessionInterface networkAgent,
-            @NonNull Phone phone) {
+    public QosCallbackTracker(@NonNull TelephonyNetworkAgent networkAgent, @NonNull Phone phone) {
         mQosBearerSessions = new HashMap<>();
         mCallbacksToFilter = new HashMap<>();
         mNetworkAgent = networkAgent;
@@ -103,40 +110,41 @@
         mRcsStats = RcsStats.getInstance();
         mLogTag = "QOSCT" + "-" + ((NetworkAgent) mNetworkAgent).getNetwork().getNetId();
 
-        if (phone.isUsingNewDataStack()) {
-            //TODO: Replace the NetworkAgent in the constructor with TelephonyNetworkAgent
-            //  after mPhone.isUsingNewDataStack() check is removed.
-            ((TelephonyNetworkAgent) networkAgent).registerCallback(
-                    new TelephonyNetworkAgent.TelephonyNetworkAgentCallback(this::post) {
-                        @Override
-                        public void onQosCallbackRegistered(int qosCallbackId,
-                                @NonNull QosFilter filter) {
-                            addFilter(qosCallbackId,
-                                    new QosCallbackTracker.IFilter() {
-                                        @Override
-                                        public boolean matchesLocalAddress(
-                                                @NonNull InetAddress address, int startPort,
-                                                int endPort) {
-                                            return filter.matchesLocalAddress(address, startPort,
-                                                    endPort);
-                                        }
+        networkAgent.registerCallback(
+                new TelephonyNetworkAgent.TelephonyNetworkAgentCallback(this::post) {
+                    @Override
+                    public void onQosCallbackRegistered(int qosCallbackId,
+                            @NonNull QosFilter filter) {
+                        addFilter(qosCallbackId,
+                                new QosCallbackTracker.IFilter() {
+                                    @Override
+                                    public boolean matchesLocalAddress(
+                                            @NonNull InetAddress address, int startPort,
+                                            int endPort) {
+                                        return filter.matchesLocalAddress(address, startPort,
+                                                endPort);
+                                    }
 
-                                        @Override
-                                        public boolean matchesRemoteAddress(
-                                                @NonNull InetAddress address, int startPort,
-                                                int endPort) {
-                                            return filter.matchesRemoteAddress(address, startPort,
-                                                    endPort);
-                                        }
-                                    });
-                        }
+                                    @Override
+                                    public boolean matchesRemoteAddress(
+                                            @NonNull InetAddress address, int startPort,
+                                            int endPort) {
+                                        return filter.matchesRemoteAddress(address, startPort,
+                                                endPort);
+                                    }
 
-                        @Override
-                        public void onQosCallbackUnregistered(int qosCallbackId) {
+                                    @Override
+                                    public boolean matchesProtocol(int protocol) {
+                                        return filter.matchesProtocol(protocol);
+                                    }
+                                });
+                    }
 
-                        }
-                    });
-        }
+                    @Override
+                    public void onQosCallbackUnregistered(int qosCallbackId) {
+
+                    }
+                });
     }
 
     /**
@@ -280,41 +288,76 @@
 
     private boolean matchesByLocalAddress(final @NonNull QosBearerFilter sessionFilter,
             final @NonNull IFilter filter) {
-        if (sessionFilter.getLocalPortRange() == null) return false;
-        for (final LinkAddress qosAddress : sessionFilter.getLocalAddresses()) {
-            return filter.matchesLocalAddress(qosAddress.getAddress(),
-                    sessionFilter.getLocalPortRange().getStart(),
-                    sessionFilter.getLocalPortRange().getEnd());
+        int portStart;
+        int portEnd;
+        if (sessionFilter.getLocalPortRange() == null) {
+            portStart = QosBearerFilter.QOS_MIN_PORT;
+            portEnd = QosBearerFilter.QOS_MAX_PORT;
+        } else if (sessionFilter.getLocalPortRange().isValid()) {
+            portStart = sessionFilter.getLocalPortRange().getStart();
+            portEnd = sessionFilter.getLocalPortRange().getEnd();
+        } else {
+            return false;
+        }
+        if (sessionFilter.getLocalAddresses().isEmpty()) {
+            InetAddress anyAddress;
+            try {
+                anyAddress = InetAddress.getByAddress(new byte[] {0, 0, 0, 0});
+            } catch (UnknownHostException e) {
+                return false;
+            }
+            return filter.matchesLocalAddress(anyAddress, portStart, portEnd);
+        } else {
+            for (final LinkAddress qosAddress : sessionFilter.getLocalAddresses()) {
+                return filter.matchesLocalAddress(qosAddress.getAddress(), portStart, portEnd);
+            }
         }
         return false;
     }
 
     private boolean matchesByRemoteAddress(@NonNull QosBearerFilter sessionFilter,
             final @NonNull IFilter filter) {
-        if (sessionFilter.getRemotePortRange() == null) return false;
-        for (final LinkAddress qosAddress : sessionFilter.getRemoteAddresses()) {
-            return filter.matchesRemoteAddress(qosAddress.getAddress(),
-                    sessionFilter.getRemotePortRange().getStart(),
-                    sessionFilter.getRemotePortRange().getEnd());
+        int portStart;
+        int portEnd;
+        boolean result = false;
+        if (sessionFilter.getRemotePortRange() == null) {
+            portStart = QosBearerFilter.QOS_MIN_PORT;
+            portEnd = QosBearerFilter.QOS_MAX_PORT;
+        } else if (sessionFilter.getRemotePortRange().isValid()) {
+            portStart = sessionFilter.getRemotePortRange().getStart();
+            portEnd = sessionFilter.getRemotePortRange().getEnd();
+        } else {
+            return false;
         }
-        return false;
-    }
-
-    private boolean matchesByRemoteAndLocalAddress(@NonNull QosBearerFilter sessionFilter,
-            final @NonNull IFilter filter) {
-        if (sessionFilter.getLocalPortRange() == null
-                || sessionFilter.getRemotePortRange() == null) return false;
-        for (final LinkAddress remoteAddress : sessionFilter.getRemoteAddresses()) {
-            for (final LinkAddress localAddress : sessionFilter.getLocalAddresses()) {
-                return filter.matchesRemoteAddress(remoteAddress.getAddress(),
-                        sessionFilter.getRemotePortRange().getStart(),
-                        sessionFilter.getRemotePortRange().getEnd())
-                        && filter.matchesLocalAddress(localAddress.getAddress(),
-                              sessionFilter.getLocalPortRange().getStart(),
-                              sessionFilter.getLocalPortRange().getEnd());
+        if (sessionFilter.getRemoteAddresses().isEmpty()) {
+            InetAddress anyAddress;
+            try {
+                anyAddress = InetAddress.getByAddress(new byte[] {0, 0, 0, 0});
+            } catch (UnknownHostException e) {
+                return false;
+            }
+            result = filter.matchesRemoteAddress(anyAddress, portStart, portEnd);
+        } else {
+            for (final LinkAddress qosAddress : sessionFilter.getRemoteAddresses()) {
+                result = filter.matchesRemoteAddress(qosAddress.getAddress(), portStart, portEnd);
             }
         }
-        return false;
+        return result;
+    }
+
+    private boolean matchesByProtocol(@NonNull QosBearerFilter sessionFilter,
+            final @NonNull IFilter filter, boolean hasMatchedFilter) {
+        boolean result = false;
+        int protocol = sessionFilter.getProtocol();
+        if (protocol == QosBearerFilter.QOS_PROTOCOL_TCP
+                || protocol == QosBearerFilter.QOS_PROTOCOL_UDP) {
+            result = filter.matchesProtocol(protocol);
+        } else {
+            // FWK currently doesn't support filtering based on protocol ID ESP & AH. We will follow
+            // match results of other filters.
+            result = hasMatchedFilter;
+        }
+        return result;
     }
 
     private QosBearerFilter getFilterByPrecedence(
@@ -329,28 +372,36 @@
         QosBearerFilter qosFilter = null;
 
         for (final QosBearerFilter sessionFilter : qosBearerSession.getQosBearerFilterList()) {
+            boolean unMatched = false;
+            boolean hasMatchedFilter = false;
             if (!sessionFilter.getLocalAddresses().isEmpty()
-                    && !sessionFilter.getRemoteAddresses().isEmpty()
-                    && sessionFilter.getLocalPortRange() != null
-                    && sessionFilter.getLocalPortRange().isValid()
-                    && sessionFilter.getRemotePortRange() != null
-                    && sessionFilter.getRemotePortRange().isValid()) {
-                if (matchesByRemoteAndLocalAddress(sessionFilter, filter)) {
-                    qosFilter = getFilterByPrecedence(qosFilter, sessionFilter);
+                    || sessionFilter.getLocalPortRange() != null) {
+                if (!matchesByLocalAddress(sessionFilter, filter)) {
+                    unMatched = true;
+                } else {
+                    hasMatchedFilter = true;
                 }
-            } else if (!sessionFilter.getRemoteAddresses().isEmpty()
-                    && sessionFilter.getRemotePortRange() != null
-                    && sessionFilter.getRemotePortRange().isValid()) {
-                if (matchesByRemoteAddress(sessionFilter, filter)) {
-                    qosFilter = getFilterByPrecedence(qosFilter, sessionFilter);
+            }
+            if (!sessionFilter.getRemoteAddresses().isEmpty()
+                    || sessionFilter.getRemotePortRange() != null) {
+                if (!matchesByRemoteAddress(sessionFilter, filter)) {
+                    unMatched = true;
+                } else {
+                    hasMatchedFilter = true;
                 }
-            } else if (!sessionFilter.getLocalAddresses().isEmpty()
-                    && sessionFilter.getLocalPortRange() != null
-                    && sessionFilter.getLocalPortRange().isValid()) {
-                if (matchesByLocalAddress(sessionFilter, filter)) {
-                    qosFilter = getFilterByPrecedence(qosFilter, sessionFilter);
+            }
+
+            if (sessionFilter.getProtocol() != QosBearerFilter.QOS_PROTOCOL_UNSPECIFIED) {
+                if (!matchesByProtocol(sessionFilter, filter, hasMatchedFilter)) {
+                    unMatched = true;
+                } else {
+                    hasMatchedFilter = true;
                 }
             }
+
+            if (!unMatched && hasMatchedFilter) {
+                qosFilter = getFilterByPrecedence(qosFilter, sessionFilter);
+            }
         }
         return qosFilter;
     }
@@ -375,7 +426,7 @@
                             qos.getDownlinkBandwidth().getGuaranteedBitrateKbps(),
                             qos.getUplinkBandwidth().getGuaranteedBitrateKbps(),
                             remoteAddresses);
-            mNetworkAgent.notifyQosSessionAvailable(
+            mNetworkAgent.sendQosSessionAvailable(
                     callbackId, session.getQosBearerSessionId(), epsBearerAttr);
         } else {
             NrQos qos = (NrQos) session.getQos();
@@ -386,7 +437,7 @@
                             qos.getDownlinkBandwidth().getGuaranteedBitrateKbps(),
                             qos.getUplinkBandwidth().getGuaranteedBitrateKbps(),
                             qos.getAveragingWindow(), remoteAddresses);
-            mNetworkAgent.notifyQosSessionAvailable(
+            mNetworkAgent.sendQosSessionAvailable(
                     callbackId, session.getQosBearerSessionId(), nrQosAttr);
         }
 
@@ -397,7 +448,7 @@
     }
 
     private void sendSessionLost(int callbackId, @NonNull QosBearerSession session) {
-        mNetworkAgent.notifyQosSessionLost(callbackId, session.getQosBearerSessionId(),
+        mNetworkAgent.sendQosSessionLost(callbackId, session.getQosBearerSessionId(),
                 session.getQos() instanceof EpsQos
                         ? QosSession.TYPE_EPS_BEARER : QosSession.TYPE_NR_BEARER);
         log("sendSessionLost, callbackId=" + callbackId);
diff --git a/src/java/com/android/internal/telephony/data/TelephonyNetworkAgent.java b/src/java/com/android/internal/telephony/data/TelephonyNetworkAgent.java
index 3b12562..5ed09e6 100644
--- a/src/java/com/android/internal/telephony/data/TelephonyNetworkAgent.java
+++ b/src/java/com/android/internal/telephony/data/TelephonyNetworkAgent.java
@@ -22,10 +22,10 @@
 import android.net.KeepalivePacketData;
 import android.net.NetworkAgent;
 import android.net.NetworkAgentConfig;
+import android.net.NetworkCapabilities;
 import android.net.NetworkProvider;
 import android.net.NetworkScore;
 import android.net.QosFilter;
-import android.net.QosSessionAttributes;
 import android.net.Uri;
 import android.os.Looper;
 import android.util.ArraySet;
@@ -46,11 +46,13 @@
  * for telephony to propagate network related information to the connectivity service. It always
  * has an associated parent {@link DataNetwork}.
  */
-public class TelephonyNetworkAgent extends NetworkAgent implements NotifyQosSessionInterface {
+public class TelephonyNetworkAgent extends NetworkAgent {
     private final String mLogTag;
-    private final Phone mPhone;
     private final LocalLog mLocalLog = new LocalLog(128);
 
+    /** Max unregister network agent delay. */
+    private static final int NETWORK_AGENT_TEARDOWN_DELAY_MS = 5_000;
+
     /** The parent data network. */
     private final @NonNull DataNetwork mDataNetwork;
 
@@ -153,18 +155,25 @@
             @NonNull NetworkAgentConfig config, @NonNull NetworkProvider provider,
             @NonNull TelephonyNetworkAgentCallback callback) {
         super(phone.getContext(), looper, "TelephonyNetworkAgent",
-                dataNetwork.getNetworkCapabilities(), dataNetwork.getLinkProperties(), score,
+                // Connectivity service does not allow an agent created in suspended state.
+                // Always create the network agent with NOT_SUSPENDED and immediately update the
+                // suspended state afterwards.
+                new NetworkCapabilities.Builder(dataNetwork.getNetworkCapabilities())
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
+                        .build(),
+                dataNetwork.getLinkProperties(), score,
                 config, provider);
         register();
         mDataNetwork = dataNetwork;
         mNetworkAgentConfig = config;
         mTelephonyNetworkAgentCallbacks.add(callback);
-        mPhone = phone;
         mId = getNetwork().getNetId();
         mLogTag = "TNA-" + mId;
 
         log("TelephonyNetworkAgent created, nc="
-                + dataNetwork.getNetworkCapabilities() + ", score=" + score);
+                + new NetworkCapabilities.Builder(dataNetwork.getNetworkCapabilities())
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
+                .build() + ", score=" + score);
     }
 
     /**
@@ -289,46 +298,16 @@
     }
 
     /**
-     * Sends the attributes of Qos Session back to the Application. This method is create for
-     * Mockito to mock since
-     * {@link NetworkAgent#sendQosSessionAvailable(int, int, QosSessionAttributes)} is
-     * {@code final} that can't be mocked.
-     *
-     * @param qosCallbackId the callback id that the session belongs to.
-     * @param sessionId the unique session id across all Qos Sessions.
-     * @param attributes the attributes of the Qos Session.
-     */
-    @Override
-    public void notifyQosSessionAvailable(final int qosCallbackId, final int sessionId,
-            @NonNull final QosSessionAttributes attributes) {
-        super.sendQosSessionAvailable(qosCallbackId, sessionId, attributes);
-    }
-
-    /**
-     * Sends event that the Qos Session was lost. This method is create for Mockito to mock
-     * since {@link NetworkAgent#sendQosSessionLost(int, int, int)} is {@code final} that can't be
-     * mocked..
-     *
-     * @param qosCallbackId the callback id that the session belongs to.
-     * @param sessionId the unique session id across all Qos Sessions.
-     * @param qosSessionType the session type {@code QosSession#QosSessionType}.
-     */
-    @Override
-    public void notifyQosSessionLost(final int qosCallbackId,
-            final int sessionId, final int qosSessionType) {
-        super.sendQosSessionLost(qosCallbackId, sessionId, qosSessionType);
-    }
-
-    /**
      * Abandon the network agent. This is used for telephony to re-create the network agent when
-     * immutable capabilities got changed, where telephony calls {@link NetworkAgent#unregister()}
-     * and then create another network agent with new capabilities. Abandon this network agent
-     * allowing it ignore the subsequent {@link #onNetworkUnwanted()} invocation caused by
-     * {@link NetworkAgent#unregister()}.
+     * immutable capabilities got changed, where telephony calls
+     * {@link NetworkAgent#unregisterAfterReplacement} and then create another network agent with
+     * new capabilities. Abandon this network agent allowing it ignore the subsequent
+     * {@link #onNetworkUnwanted()} invocation caused by
+     * {@link NetworkAgent#unregisterAfterReplacement}.
      */
     public void abandon() {
         mAbandoned = true;
-        unregister();
+        unregisterAfterReplacement(NETWORK_AGENT_TEARDOWN_DELAY_MS);
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/data/TelephonyNetworkFactory.java b/src/java/com/android/internal/telephony/data/TelephonyNetworkFactory.java
index 85f0ae1..2cdf807 100644
--- a/src/java/com/android/internal/telephony/data/TelephonyNetworkFactory.java
+++ b/src/java/com/android/internal/telephony/data/TelephonyNetworkFactory.java
@@ -20,27 +20,15 @@
 import android.net.NetworkFactory;
 import android.net.NetworkRequest;
 import android.net.TelephonyNetworkSpecifier;
-import android.os.AsyncResult;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.telephony.AccessNetworkConstants;
-import android.telephony.Annotation.ApnType;
 import android.telephony.SubscriptionManager;
-import android.telephony.data.ApnSetting;
 import android.util.LocalLog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.PhoneFactory;
-import com.android.internal.telephony.SubscriptionController;
-import com.android.internal.telephony.dataconnection.ApnContext;
-import com.android.internal.telephony.dataconnection.DataConnection;
-import com.android.internal.telephony.dataconnection.DcTracker;
-import com.android.internal.telephony.dataconnection.DcTracker.ReleaseNetworkType;
-import com.android.internal.telephony.dataconnection.DcTracker.RequestNetworkType;
-import com.android.internal.telephony.dataconnection.TransportManager.HandoverParams;
 import com.android.internal.telephony.metrics.NetworkRequestsStats;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.telephony.Rlog;
@@ -72,19 +60,14 @@
     public static final int EVENT_SUBSCRIPTION_CHANGED              = 2;
     private static final int EVENT_NETWORK_REQUEST                  = 3;
     private static final int EVENT_NETWORK_RELEASE                  = 4;
-    private static final int EVENT_DATA_HANDOVER_NEEDED             = 5;
-    private static final int EVENT_DATA_HANDOVER_COMPLETED          = 6;
 
     private final PhoneSwitcher mPhoneSwitcher;
-    private final SubscriptionController mSubscriptionController;
     private final LocalLog mLocalLog = new LocalLog(REQUEST_LOG_SIZE);
 
-    // Key: network request. Value: the transport of DcTracker it applies to,
+    // Key: network request. Value: the transport of the network request applies to,
     // AccessNetworkConstants.TRANSPORT_TYPE_INVALID if not applied.
     private final Map<TelephonyNetworkRequest, Integer> mNetworkRequests = new HashMap<>();
 
-    private final Map<Message, HandoverParams> mPendingHandovers = new HashMap<>();
-
     private final Phone mPhone;
 
     private AccessNetworksManager mAccessNetworksManager;
@@ -101,10 +84,9 @@
         mPhone = phone;
         mInternalHandler = new InternalHandler(looper);
 
-        mSubscriptionController = SubscriptionController.getInstance();
         mAccessNetworksManager = mPhone.getAccessNetworksManager();
 
-        setCapabilityFilter(makeNetworkFilter(mSubscriptionController, mPhone.getPhoneId()));
+        setCapabilityFilter(makeNetworkFilterByPhoneId(mPhone.getPhoneId()));
         setScoreFilter(TELEPHONY_NETWORK_SCORE);
 
         mPhoneSwitcher = PhoneSwitcher.getInstance();
@@ -112,10 +94,6 @@
 
         mPhoneSwitcher.registerForActivePhoneSwitch(mInternalHandler, EVENT_ACTIVE_PHONE_SWITCH,
                 null);
-        if (!phone.isUsingNewDataStack()) {
-            mPhone.getTransportManager().registerForHandoverNeededEvent(mInternalHandler,
-                    EVENT_DATA_HANDOVER_NEEDED);
-        }
 
         mSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
         SubscriptionManager.from(mPhone.getContext()).addOnSubscriptionsChangedListener(
@@ -132,10 +110,8 @@
                 }
             };
 
-    private NetworkCapabilities makeNetworkFilter(SubscriptionController subscriptionController,
-            int phoneId) {
-        final int subscriptionId = subscriptionController.getSubIdUsingPhoneId(phoneId);
-        return makeNetworkFilter(subscriptionId);
+    private NetworkCapabilities makeNetworkFilterByPhoneId(int phoneId) {
+        return makeNetworkFilter(SubscriptionManager.getSubscriptionId(phoneId));
     }
 
     /**
@@ -201,93 +177,18 @@
                     onReleaseNetworkFor(msg);
                     break;
                 }
-                case EVENT_DATA_HANDOVER_NEEDED: {
-                    AsyncResult ar = (AsyncResult) msg.obj;
-                    HandoverParams handoverParams = (HandoverParams) ar.result;
-                    onDataHandoverNeeded(handoverParams.apnType, handoverParams.targetTransport,
-                            handoverParams);
-                    break;
-                }
-                case EVENT_DATA_HANDOVER_COMPLETED: {
-                    Bundle bundle = msg.getData();
-                    NetworkRequest nr = bundle.getParcelable(
-                            DcTracker.DATA_COMPLETE_MSG_EXTRA_NETWORK_REQUEST);
-                    boolean success = bundle.getBoolean(
-                            DcTracker.DATA_COMPLETE_MSG_EXTRA_SUCCESS);
-                    int transport = bundle.getInt(
-                            DcTracker.DATA_COMPLETE_MSG_EXTRA_TRANSPORT_TYPE);
-                    boolean fallback = bundle.getBoolean(
-                            DcTracker.DATA_COMPLETE_MSG_EXTRA_HANDOVER_FAILURE_FALLBACK);
-                    HandoverParams handoverParams = mPendingHandovers.remove(msg);
-                    if (handoverParams != null) {
-                        onDataHandoverSetupCompleted(nr, success, transport, fallback,
-                                handoverParams);
-                    } else {
-                        logl("Handover completed but cannot find handover entry!");
-                    }
-                    break;
-                }
             }
         }
     }
 
     private int getTransportTypeFromNetworkRequest(TelephonyNetworkRequest networkRequest) {
-        if (PhoneFactory.getDefaultPhone().isUsingNewDataStack()) {
-            int transport = AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
-            int capability = networkRequest.getApnTypeNetworkCapability();
-            if (capability >= 0) {
-                transport = mAccessNetworksManager
-                        .getPreferredTransportByNetworkCapability(capability);
-            }
-            return transport;
-        } else {
-            int apnType = ApnContext.getApnTypeFromNetworkRequest(
-                    networkRequest.getNativeNetworkRequest());
-            return mAccessNetworksManager.getCurrentTransport(apnType);
+        int transport = AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+        int capability = networkRequest.getApnTypeNetworkCapability();
+        if (capability >= 0) {
+            transport = mAccessNetworksManager
+                    .getPreferredTransportByNetworkCapability(capability);
         }
-    }
-
-    /**
-     * Request network
-     *
-     * @param networkRequest Network request from clients
-     * @param requestType The request type
-     * @param transport Transport type
-     * @param onHandoverCompleteMsg When request type is handover, this message will be sent when
-     * handover is completed. For normal request, this should be null.
-     */
-    private void requestNetworkInternal(TelephonyNetworkRequest networkRequest,
-            @RequestNetworkType int requestType, int transport, Message onHandoverCompleteMsg) {
-        NetworkRequestsStats.addNetworkRequest(networkRequest.getNativeNetworkRequest(),
-                mSubscriptionId);
-
-        if (mPhone.isUsingNewDataStack()) {
-            mPhone.getDataNetworkController().addNetworkRequest(networkRequest);
-        } else {
-            if (mPhone.getDcTracker(transport) != null) {
-                mPhone.getDcTracker(transport).requestNetwork(
-                        networkRequest.getNativeNetworkRequest(), requestType,
-                        onHandoverCompleteMsg);
-            }
-        }
-    }
-
-    private void releaseNetworkInternal(TelephonyNetworkRequest networkRequest) {
-        mPhone.getDataNetworkController().removeNetworkRequest(networkRequest);
-    }
-
-    // TODO: Clean this up after old data stack removed.
-    private void releaseNetworkInternal(TelephonyNetworkRequest networkRequest,
-                                        @ReleaseNetworkType int releaseType,
-                                        int transport) {
-        if (mPhone.isUsingNewDataStack()) {
-            mPhone.getDataNetworkController().removeNetworkRequest(networkRequest);
-        } else {
-            if (mPhone.getDcTracker(transport) != null) {
-                mPhone.getDcTracker(transport).releaseNetwork(
-                        networkRequest.getNativeNetworkRequest(), releaseType);
-            }
-        }
+        return transport;
     }
 
     private static int getAction(boolean wasActive, boolean isActive) {
@@ -317,15 +218,11 @@
                     ? "Requesting" : "Releasing") + " network request " + networkRequest);
             int transportType = getTransportTypeFromNetworkRequest(networkRequest);
             if (action == ACTION_REQUEST) {
-                requestNetworkInternal(networkRequest, DcTracker.REQUEST_TYPE_NORMAL,
-                        getTransportTypeFromNetworkRequest(networkRequest), null);
+                NetworkRequestsStats.addNetworkRequest(networkRequest.getNativeNetworkRequest(),
+                        mSubscriptionId);
+                mPhone.getDataNetworkController().addNetworkRequest(networkRequest);
             } else if (action == ACTION_RELEASE) {
-                if (mPhone.isUsingNewDataStack()) {
-                    releaseNetworkInternal(networkRequest);
-                } else {
-                    releaseNetworkInternal(networkRequest, DcTracker.RELEASE_TYPE_DETACH,
-                            getTransportTypeFromNetworkRequest(networkRequest));
-                }
+                mPhone.getDataNetworkController().removeNetworkRequest(networkRequest);
             }
 
             mNetworkRequests.put(networkRequest,
@@ -336,8 +233,7 @@
     // watch for phone->subId changes, reapply new filter and let
     // that flow through to apply/revoke of requests
     private void onSubIdChange() {
-        final int newSubscriptionId = mSubscriptionController.getSubIdUsingPhoneId(
-                mPhone.getPhoneId());
+        int newSubscriptionId = SubscriptionManager.getSubscriptionId(mPhone.getPhoneId());
         if (mSubscriptionId != newSubscriptionId) {
             if (DBG) logl("onSubIdChange " + mSubscriptionId + "->" + newSubscriptionId);
             mSubscriptionId = newSubscriptionId;
@@ -365,8 +261,9 @@
         logl("onNeedNetworkFor " + networkRequest + " shouldApply " + shouldApply);
 
         if (shouldApply) {
-            requestNetworkInternal(networkRequest, DcTracker.REQUEST_TYPE_NORMAL,
-                    getTransportTypeFromNetworkRequest(networkRequest), null);
+            NetworkRequestsStats.addNetworkRequest(networkRequest.getNativeNetworkRequest(),
+                    mSubscriptionId);
+            mPhone.getDataNetworkController().addNetworkRequest(networkRequest);
         }
     }
 
@@ -388,136 +285,10 @@
         logl("onReleaseNetworkFor " + networkRequest + " applied " + applied);
 
         if (applied) {
-            if (mPhone.isUsingNewDataStack()) {
-                releaseNetworkInternal(networkRequest);
-            } else {
-                // Most of the time, the network request only exists in one of the DcTracker, but in
-                // the middle of handover, the network request temporarily exists in both
-                // DcTrackers. If connectivity service releases the network request while handover
-                // is ongoing, we need to remove network requests from both DcTrackers.
-                // Note that this part will be refactored in T, where we won't even have DcTracker
-                // at all.
-                releaseNetworkInternal(networkRequest, DcTracker.RELEASE_TYPE_NORMAL,
-                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-                releaseNetworkInternal(networkRequest, DcTracker.RELEASE_TYPE_NORMAL,
-                        AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
-            }
+            mPhone.getDataNetworkController().removeNetworkRequest(networkRequest);
         }
     }
 
-    private void onDataHandoverNeeded(@ApnType int apnType, int targetTransport,
-                                      HandoverParams handoverParams) {
-        log("onDataHandoverNeeded: apnType=" + ApnSetting.getApnTypeString(apnType)
-                + ", target transport="
-                + AccessNetworkConstants.transportTypeToString(targetTransport));
-        if (mAccessNetworksManager.getCurrentTransport(apnType) == targetTransport) {
-            log("APN type " + ApnSetting.getApnTypeString(apnType) + " is already on "
-                    + AccessNetworkConstants.transportTypeToString(targetTransport));
-            return;
-        }
-
-        boolean handoverPending = false;
-        for (Map.Entry<TelephonyNetworkRequest, Integer> entry : mNetworkRequests.entrySet()) {
-            TelephonyNetworkRequest networkRequest = entry.getKey();
-            int currentTransport = entry.getValue();
-            boolean applied = currentTransport != AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
-            if (ApnContext.getApnTypeFromNetworkRequest(
-                    networkRequest.getNativeNetworkRequest()) == apnType
-                    && applied
-                    && currentTransport != targetTransport) {
-                DcTracker dcTracker = mPhone.getDcTracker(currentTransport);
-                if (dcTracker != null) {
-                    DataConnection dc = dcTracker.getDataConnectionByApnType(
-                            ApnSetting.getApnTypeString(apnType));
-                    if (dc != null && (dc.isActive())) {
-                        Message onCompleteMsg = mInternalHandler.obtainMessage(
-                                EVENT_DATA_HANDOVER_COMPLETED);
-                        onCompleteMsg.getData().putParcelable(
-                                DcTracker.DATA_COMPLETE_MSG_EXTRA_NETWORK_REQUEST,
-                                networkRequest.getNativeNetworkRequest());
-                        mPendingHandovers.put(onCompleteMsg, handoverParams);
-                        requestNetworkInternal(networkRequest, DcTracker.REQUEST_TYPE_HANDOVER,
-                                targetTransport, onCompleteMsg);
-                        log("Requested handover " + ApnSetting.getApnTypeString(apnType)
-                                + " to "
-                                + AccessNetworkConstants.transportTypeToString(targetTransport)
-                                + ". " + networkRequest);
-                        handoverPending = true;
-                    } else {
-                        // Request is there, but no actual data connection. In this case, just move
-                        // the request to the new transport.
-                        log("The network request is on transport " + AccessNetworkConstants
-                                .transportTypeToString(currentTransport) + ", but no live data "
-                                + "connection. Just move the request to transport "
-                                + AccessNetworkConstants.transportTypeToString(targetTransport)
-                                + ", dc=" + dc);
-                        entry.setValue(targetTransport);
-                        releaseNetworkInternal(networkRequest, DcTracker.RELEASE_TYPE_NORMAL,
-                                currentTransport);
-                        requestNetworkInternal(networkRequest, DcTracker.REQUEST_TYPE_NORMAL,
-                                targetTransport, null);
-                    }
-                } else {
-                    log("DcTracker on " + AccessNetworkConstants.transportTypeToString(
-                            currentTransport) + " is not available.");
-                }
-            }
-        }
-
-        if (!handoverPending) {
-            log("No handover request pending. Handover process is now completed");
-            handoverParams.callback.onCompleted(true, false);
-        }
-    }
-
-    private void onDataHandoverSetupCompleted(NetworkRequest request, boolean success,
-                                              int targetTransport, boolean fallback,
-                                              HandoverParams handoverParams) {
-        log("onDataHandoverSetupCompleted: " + request + ", success=" + success
-                + ", targetTransport="
-                + AccessNetworkConstants.transportTypeToString(targetTransport)
-                + ", fallback=" + fallback);
-
-        TelephonyNetworkRequest networkRequest = new TelephonyNetworkRequest(request, mPhone);
-        // At this point, handover setup has been completed on the target transport.
-        // If it succeeded, or it failed without falling back to the original transport,
-        // we should release the request from the original transport.
-        if (!fallback) {
-            int originTransport = DataUtils.getSourceTransport(targetTransport);
-            int releaseType = success
-                    ? DcTracker.RELEASE_TYPE_HANDOVER
-                    // If handover fails, we need to tear down the existing connection, so the
-                    // new data connection can be re-established on the new transport. If we leave
-                    // the existing data connection in current transport, then DCT and qualified
-                    // network service will be out of sync. Specifying release type to detach
-                    // the transport is moved to the other transport, but network request is still
-                    // there, connectivity service will not call unwanted to tear down the network.
-                    // We need explicitly tear down the data connection here so the new data
-                    // connection can be re-established on the other transport.
-                    : DcTracker.RELEASE_TYPE_DETACH;
-            releaseNetworkInternal(networkRequest, releaseType, originTransport);
-
-            // Before updating the network request with the target transport, make sure the request
-            // is still there because it's possible that connectivity service has already released
-            // the network while handover is ongoing. If connectivity service already released
-            // the network request, we need to tear down the just-handovered data connection on the
-            // target transport.
-            if (mNetworkRequests.containsKey(networkRequest)) {
-                // Update it with the target transport.
-                mNetworkRequests.put(networkRequest, targetTransport);
-            }
-        } else {
-            // If handover fails and requires to fallback, the context of target transport needs to
-            // be released
-            if (!success) {
-                releaseNetworkInternal(networkRequest,
-                        DcTracker.RELEASE_TYPE_NORMAL, targetTransport);
-            }
-        }
-
-        handoverParams.callback.onCompleted(success, fallback);
-    }
-
     protected void log(String s) {
         Rlog.d(LOG_TAG, s);
     }
diff --git a/src/java/com/android/internal/telephony/data/TelephonyNetworkRequest.java b/src/java/com/android/internal/telephony/data/TelephonyNetworkRequest.java
index b55304a..b334b89 100644
--- a/src/java/com/android/internal/telephony/data/TelephonyNetworkRequest.java
+++ b/src/java/com/android/internal/telephony/data/TelephonyNetworkRequest.java
@@ -157,8 +157,7 @@
     /**
      * Data config manager for retrieving data config.
      */
-    // TODO: Make this @NonNull after old data stack removed.
-    private final @Nullable DataConfigManager mDataConfigManager;
+    private final @NonNull DataConfigManager mDataConfigManager;
 
     /**
      * The attached data network. Note that the data network could be in any state. {@code null}
@@ -204,12 +203,8 @@
         // to satisfy it.
         mState = REQUEST_STATE_UNSATISFIED;
         mCreatedTimeMillis = SystemClock.elapsedRealtime();
-        if (phone.isUsingNewDataStack()) {
-            mDataConfigManager = phone.getDataNetworkController().getDataConfigManager();
-            updatePriority();
-        } else {
-            mDataConfigManager = null;
-        }
+        mDataConfigManager = phone.getDataNetworkController().getDataConfigManager();
+        updatePriority();
     }
 
     /**
@@ -401,8 +396,7 @@
      * @return {@code true} if this network request can result in bringing up a metered network.
      */
     public boolean isMeteredRequest() {
-        // TODO: Remove null check after old data stack removed.
-        return mDataConfigManager != null && mDataConfigManager.isAnyMeteredCapability(
+        return mDataConfigManager.isAnyMeteredCapability(
                 getCapabilities(), mPhone.getServiceState().getDataRoaming());
     }
 
diff --git a/src/java/com/android/internal/telephony/dataconnection/ApnConfigType.java b/src/java/com/android/internal/telephony/dataconnection/ApnConfigType.java
deleted file mode 100644
index 827dbdd..0000000
--- a/src/java/com/android/internal/telephony/dataconnection/ApnConfigType.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.telephony.dataconnection;
-
-import android.telephony.Annotation;
-
-/**
- * Container of network configuration settings relevant for telephony module.
- *
- */
-public class ApnConfigType {
-
-    private final int mType;
-    private final int mPriority;
-
-    public ApnConfigType(@Annotation.ApnType int type, int priority) {
-        mType = type;
-        mPriority = priority;
-    }
-
-    /**
-     * Returns the apn type of this config type
-     * @return Type of apn.
-     */
-    public int getType() {
-        return mType;
-    }
-
-    /**
-     * Returns the priority of this apn config type.
-     * @return The priority of this apn.
-     */
-    public int getPriority() {
-        return mPriority;
-    }
-}
diff --git a/src/java/com/android/internal/telephony/dataconnection/ApnConfigTypeRepository.java b/src/java/com/android/internal/telephony/dataconnection/ApnConfigTypeRepository.java
deleted file mode 100644
index 156ac92..0000000
--- a/src/java/com/android/internal/telephony/dataconnection/ApnConfigTypeRepository.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.telephony.dataconnection;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.PersistableBundle;
-import android.telephony.Annotation;
-import android.telephony.CarrierConfigManager;
-import android.telephony.Rlog;
-import android.telephony.data.ApnSetting;
-import android.util.ArrayMap;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Hard coded configuration of specific network types that the telephony module needs.
- * Formerly stored in network attributes within the resources file.
- */
-public class ApnConfigTypeRepository {
-
-    private static final String TAG = ApnConfigTypeRepository.class.getSimpleName();
-
-    private final Map<Integer, ApnConfigType> mConfigTypeMap;
-
-    public ApnConfigTypeRepository(PersistableBundle carrierConfig) {
-        mConfigTypeMap = new HashMap<>();
-        setup(carrierConfig);
-    }
-
-    /**
-     * Gets list of apn config types.
-     * @return All apn config types.
-     */
-    public Collection<ApnConfigType> getTypes() {
-        return mConfigTypeMap.values();
-    }
-
-    /**
-     * Gets the apn config type by apn type.
-     * @param type The ApnType to search for.
-     * @return The config type matching the given apn type.
-     */
-    @Nullable
-    public ApnConfigType getByType(@Annotation.ApnType int type) {
-        return mConfigTypeMap.get(type);
-    }
-
-    private void setup(PersistableBundle carrierConfig) {
-        addApns(getCarrierApnTypeMap(CarrierConfigManager.getDefaultConfig()));
-        addApns(getCarrierApnTypeMap(carrierConfig));
-    }
-
-    private void addApns(Map<Integer, Integer> apnTypeMap) {
-        add(ApnSetting.TYPE_DEFAULT, apnTypeMap);
-        add(ApnSetting.TYPE_MMS, apnTypeMap);
-        add(ApnSetting.TYPE_SUPL, apnTypeMap);
-        add(ApnSetting.TYPE_DUN, apnTypeMap);
-        add(ApnSetting.TYPE_HIPRI, apnTypeMap);
-        add(ApnSetting.TYPE_FOTA, apnTypeMap);
-        add(ApnSetting.TYPE_IMS, apnTypeMap);
-        add(ApnSetting.TYPE_CBS, apnTypeMap);
-        add(ApnSetting.TYPE_IA, apnTypeMap);
-        add(ApnSetting.TYPE_EMERGENCY, apnTypeMap);
-        add(ApnSetting.TYPE_MCX, apnTypeMap);
-        add(ApnSetting.TYPE_XCAP, apnTypeMap);
-        add(ApnSetting.TYPE_ENTERPRISE, apnTypeMap);
-    }
-
-    @NonNull
-    private Map<Integer, Integer> getCarrierApnTypeMap(PersistableBundle carrierConfig) {
-        if (carrierConfig == null) {
-            Rlog.w(TAG, "carrier config is null");
-            return new ArrayMap<>();
-        }
-
-        final String[] apnTypeConfig =
-                carrierConfig.getStringArray(CarrierConfigManager.KEY_APN_PRIORITY_STRING_ARRAY);
-
-        final Map<Integer, Integer> apnTypeMap = new ArrayMap<>();
-        if (apnTypeConfig != null) {
-            for (final String entry : apnTypeConfig) {
-                try {
-                    final String[] keyValue = entry.split(":");
-                    if (keyValue.length != 2) {
-                        Rlog.e(TAG, "Apn type entry must have exactly one ':'");
-                    } else if (keyValue[0].contains(",")) {
-                        //getApnTypesBitmaskFromString parses commas to a list, not valid here.
-                        Rlog.e(TAG, "Invalid apn type name, entry: " + entry);
-                    } else {
-                        int apnTypeBitmask = ApnSetting.getApnTypesBitmaskFromString(keyValue[0]);
-                        if (apnTypeBitmask > 0) {
-                            apnTypeMap.put(apnTypeBitmask, Integer.parseInt(keyValue[1]));
-                        } else {
-                            Rlog.e(TAG, "Invalid apn type name, entry: " + entry);
-                        }
-                    }
-
-                } catch (Exception ex) {
-                    Rlog.e(TAG, "Exception on apn type entry: " + entry + "\n", ex);
-                }
-            }
-        }
-        return apnTypeMap;
-    }
-
-    private void add(@Annotation.ApnType int type, Map<Integer, Integer> apnTypeMap) {
-        if (apnTypeMap.containsKey(type)) {
-            mConfigTypeMap.put(type, new ApnConfigType(type, apnTypeMap.get(type)));
-        }
-    }
-}
diff --git a/src/java/com/android/internal/telephony/dataconnection/ApnContext.java b/src/java/com/android/internal/telephony/dataconnection/ApnContext.java
deleted file mode 100644
index 3f487cd..0000000
--- a/src/java/com/android/internal/telephony/dataconnection/ApnContext.java
+++ /dev/null
@@ -1,675 +0,0 @@
-/*
- * Copyright (C) 2006 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.dataconnection;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.NetworkCapabilities;
-import android.net.NetworkRequest;
-import android.os.Message;
-import android.telephony.Annotation.ApnType;
-import android.telephony.data.ApnSetting;
-import android.text.TextUtils;
-import android.util.ArraySet;
-import android.util.LocalLog;
-import android.util.SparseIntArray;
-
-import com.android.internal.R;
-import com.android.internal.telephony.DctConstants;
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.RetryManager;
-import com.android.internal.telephony.dataconnection.DcTracker.ReleaseNetworkType;
-import com.android.internal.telephony.dataconnection.DcTracker.RequestNetworkType;
-import com.android.internal.util.IndentingPrintWriter;
-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.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * Maintain the Apn context
- */
-public class ApnContext {
-
-    public final String LOG_TAG;
-    private final static String SLOG_TAG = "ApnContext";
-
-    protected static final boolean DBG = false;
-
-    private final Phone mPhone;
-
-    private final String mApnType;
-
-    private DctConstants.State mState;
-
-    private int mPriority;
-
-    private ApnSetting mApnSetting;
-
-    private DataConnection mDataConnection;
-
-    private String mReason;
-
-    /**
-     * user/app requested connection on this APN
-     */
-    AtomicBoolean mDataEnabled;
-
-    private final Object mRefCountLock = new Object();
-
-    private final DcTracker mDcTracker;
-
-
-    /**
-     * Remember this as a change in this value to a more permissive state
-     * should cause us to retry even permanent failures
-     */
-    private boolean mConcurrentVoiceAndDataAllowed;
-
-    /**
-     * used to track a single connection request so disconnects can get ignored if
-     * obsolete.
-     */
-    private final AtomicInteger mConnectionGeneration = new AtomicInteger(0);
-
-    /**
-     * Retry manager that handles the APN retry and delays.
-     */
-    private final RetryManager mRetryManager;
-
-    /**
-     * ApnContext constructor
-     * @param phone phone object
-     * @param typeId APN type Id
-     * @param logTag Tag for logging
-     * @param tracker Data call tracker
-     * @param priority Priority of APN type
-     */
-    public ApnContext(Phone phone, int typeId, String logTag, DcTracker tracker, int priority) {
-        this(phone, ApnSetting.getApnTypeString(typeId), logTag, tracker, priority);
-    }
-
-    /**
-     * ApnContext constructor
-     * @param phone phone object
-     * @param apnType APN type (e.g. default, supl, mms, etc...)
-     * @param logTag Tag for logging
-     * @param tracker Data call tracker
-     * @param priority Priority of APN type
-     */
-    public ApnContext(Phone phone, String apnType, String logTag, DcTracker tracker, int priority) {
-        mPhone = phone;
-        mApnType = apnType;
-        mState = DctConstants.State.IDLE;
-        setReason(Phone.REASON_DATA_ENABLED);
-        mDataEnabled = new AtomicBoolean(false);
-        mPriority = priority;
-        LOG_TAG = logTag;
-        mDcTracker = tracker;
-        mRetryManager = new RetryManager(phone, tracker.getDataThrottler(),
-                ApnSetting.getApnTypesBitmaskFromString(apnType));
-    }
-
-
-
-    /**
-     * Get the APN type
-     * @return The APN type
-     */
-    public String getApnType() {
-        return mApnType;
-    }
-
-    /**
-     * Gets the APN type bitmask.
-     * @return The APN type bitmask
-     */
-    public int getApnTypeBitmask() {
-        return ApnSetting.getApnTypesBitmaskFromString(mApnType);
-    }
-
-    /**
-     * Get the associated data connection
-     * @return The data connection
-     */
-    public synchronized DataConnection getDataConnection() {
-        return mDataConnection;
-    }
-
-    /**
-     * This priority is taken into account when concurrent data connections are not allowed.  The
-     * APN with the HIGHER priority is given preference.
-     * @return The priority of the APN type
-     */
-    public int getPriority() {
-        return mPriority;
-    }
-
-    /**
-     * Updates the priority of this context.
-     * @param priority The priority of the APN type
-     */
-    public void setPriority(int priority) {
-        mPriority = priority;
-    }
-
-    /**
-     * Keeping for backwards compatibility and in case it's needed in the future
-     * @return true
-     */
-    public boolean isDependencyMet() {
-        return true;
-    }
-
-    /**
-     * Set the associated data connection.
-     * @param dc data connection
-     */
-    public synchronized void setDataConnection(DataConnection dc) {
-        log("setDataConnectionAc: old=" + mDataConnection + ",new=" + dc + " this=" + this);
-        mDataConnection = dc;
-    }
-
-    /**
-     * Release data connection.
-     * @param reason The reason of releasing data connection
-     */
-    public synchronized void releaseDataConnection(String reason) {
-        if (mDataConnection != null) {
-            mDataConnection.tearDown(this, reason, null);
-            mDataConnection = null;
-        }
-        setState(DctConstants.State.IDLE);
-    }
-
-    /**
-     * Get the current APN setting.
-     * @return APN setting
-     */
-    public synchronized ApnSetting getApnSetting() {
-        log("getApnSetting: apnSetting=" + mApnSetting);
-        return mApnSetting;
-    }
-
-    /**
-     * Set the APN setting.
-     * @param apnSetting APN setting
-     */
-    public synchronized void setApnSetting(ApnSetting apnSetting) {
-        log("setApnSetting: apnSetting=" + apnSetting);
-        mApnSetting = apnSetting;
-    }
-
-    /**
-     * Set the list of APN candidates which will be used for data call setup later.
-     * @param waitingApns List of APN candidates
-     */
-    public synchronized void setWaitingApns(ArrayList<ApnSetting> waitingApns) {
-        mRetryManager.setWaitingApns(waitingApns);
-    }
-
-    /**
-     * Get the next available APN to try.
-     * @return APN setting which will be used for data call setup.{@code null} if there is no
-     * APN can be retried.
-     */
-    public @Nullable ApnSetting getNextApnSetting() {
-        return mRetryManager.getNextApnSetting();
-    }
-
-    /**
-     * Get the delay for trying the next APN setting if the current one failed.
-     * @param failFastEnabled True if fail fast mode enabled. In this case we'll use a shorter
-     * delay.
-     * @return The delay in milliseconds
-     */
-    public long getDelayForNextApn(boolean failFastEnabled) {
-        return mRetryManager.getDelayForNextApn(failFastEnabled || isFastRetryReason());
-    }
-
-    /**
-     * Mark the current APN setting permanently failed, which means it will not be retried anymore.
-     * @param apn APN setting
-     */
-    public void markApnPermanentFailed(ApnSetting apn) {
-        mRetryManager.markApnPermanentFailed(apn);
-    }
-
-    /**
-     * Get the list of waiting APNs.
-     * @return the list of waiting APNs
-     */
-    public @NonNull ArrayList<ApnSetting> getWaitingApns() {
-        return mRetryManager.getWaitingApns();
-    }
-
-    /**
-     * Save the state indicating concurrent voice/data allowed.
-     * @param allowed True if concurrent voice/data is allowed
-     */
-    public synchronized void setConcurrentVoiceAndDataAllowed(boolean allowed) {
-        mConcurrentVoiceAndDataAllowed = allowed;
-    }
-
-    /**
-     * Get the state indicating concurrent voice/data allowed.
-     * @return True if concurrent voice/data is allowed
-     */
-    public synchronized boolean isConcurrentVoiceAndDataAllowed() {
-        return mConcurrentVoiceAndDataAllowed;
-    }
-
-    /**
-     * Set the current data call state.
-     * @param s Current data call state
-     */
-    public synchronized void setState(DctConstants.State s) {
-        log("setState: " + s + ", previous state:" + mState);
-
-        if (mState != s) {
-            mStateLocalLog.log("State changed from " + mState + " to " + s);
-            mState = s;
-        }
-
-        if (mState == DctConstants.State.FAILED) {
-            // when teardown the connection and set to IDLE
-            mRetryManager.getWaitingApns().clear();
-        }
-    }
-
-    /**
-     * Get the current data call state.
-     * @return The current data call state
-     */
-    public synchronized DctConstants.State getState() {
-        return mState;
-    }
-
-    /**
-     * Check whether the data call is disconnected or not.
-     * @return True if the data call is disconnected
-     */
-    public boolean isDisconnected() {
-        DctConstants.State currentState = getState();
-        return ((currentState == DctConstants.State.IDLE) ||
-                    currentState == DctConstants.State.FAILED);
-    }
-
-    /**
-     * Set the reason for data call connection.
-     * @param reason Reason for data call connection
-     */
-    public synchronized void setReason(String reason) {
-        log("set reason as " + reason + ",current state " + mState);
-        mReason = reason;
-    }
-
-    /**
-     * Get the reason for data call connection.
-     * @return The reason for data call connection
-     */
-    public synchronized String getReason() {
-        return mReason;
-    }
-
-    /**
-     * Check if ready for data call connection
-     * @return True if ready, otherwise false.
-     */
-    public boolean isReady() {
-        return mDataEnabled.get() && isDependencyMet();
-    }
-
-    /**
-     * Check if the data call is in the state which allow connecting.
-     * @return True if allowed, otherwise false.
-     */
-    public boolean isConnectable() {
-        return isReady() && ((mState == DctConstants.State.IDLE)
-                                || (mState == DctConstants.State.RETRYING)
-                                || (mState == DctConstants.State.FAILED));
-    }
-
-    /**
-     * Check if apn reason is fast retry reason which should apply shorter delay between apn re-try.
-     * @return True if it is fast retry reason, otherwise false.
-     */
-    private boolean isFastRetryReason() {
-        return Phone.REASON_NW_TYPE_CHANGED.equals(mReason) ||
-                Phone.REASON_APN_CHANGED.equals(mReason);
-    }
-
-    /** Check if the data call is in connected or connecting state.
-     * @return True if the data call is in connected or connecting state
-     */
-    public boolean isConnectedOrConnecting() {
-        return isReady() && ((mState == DctConstants.State.CONNECTED)
-                                || (mState == DctConstants.State.CONNECTING)
-                                || (mState == DctConstants.State.RETRYING));
-    }
-
-    /**
-     * Set data call enabled/disabled state.
-     * @param enabled True if data call is enabled
-     */
-    public void setEnabled(boolean enabled) {
-        log("set enabled as " + enabled + ", current state is " + mDataEnabled.get());
-        mDataEnabled.set(enabled);
-    }
-
-    /**
-     * Check if the data call is enabled or not.
-     * @return True if enabled
-     */
-    public boolean isEnabled() {
-        return mDataEnabled.get();
-    }
-
-    public boolean isProvisioningApn() {
-        String provisioningApn = mPhone.getContext().getResources()
-                .getString(R.string.mobile_provisioning_apn);
-        if (!TextUtils.isEmpty(provisioningApn) &&
-                (mApnSetting != null) && (mApnSetting.getApnName() != null)) {
-            return (mApnSetting.getApnName().equals(provisioningApn));
-        } else {
-            return false;
-        }
-    }
-
-    private final ArraySet<NetworkRequest> mNetworkRequests = new ArraySet<>();
-    private final LocalLog mStateLocalLog = new LocalLog(32);
-
-    private static final LocalLog sLocalLog = new LocalLog(256);
-
-    /** Add a line to the ApnContext local log. */
-    public static void requestLog(ApnContext apnContext, String str) {
-        if (apnContext != null) {
-            String logString = "[ApnContext:" + apnContext.getApnType() + "] " + str;
-            if (DBG) {
-                Rlog.d(SLOG_TAG, logString);
-            }
-            synchronized (sLocalLog) {
-                sLocalLog.log(logString);
-            }
-        }
-    }
-
-    /**
-     * Request a network
-     *
-     * @param networkRequest Network request from clients
-     * @param type The request type
-     * @param onHandoverCompleteMsg When request type is handover, this message will be sent when
-     * handover is completed. For normal request, this should be null.
-     */
-    public void requestNetwork(NetworkRequest networkRequest, @RequestNetworkType int type,
-            Message onHandoverCompleteMsg) {
-        synchronized (mRefCountLock) {
-            mNetworkRequests.add(networkRequest);
-            requestLog(this, "requestNetwork for " + networkRequest + ", type="
-                    + DcTracker.requestTypeToString(type));
-            mDcTracker.enableApn(ApnSetting.getApnTypesBitmaskFromString(mApnType), type,
-                    onHandoverCompleteMsg);
-            if (mDataConnection != null) {
-                // New network request added. Should re-evaluate properties of
-                // the data connection. For example, the score may change.
-                mDataConnection.reevaluateDataConnectionProperties();
-            }
-        }
-    }
-
-    public void releaseNetwork(NetworkRequest networkRequest, @ReleaseNetworkType int type) {
-        synchronized (mRefCountLock) {
-            if (mNetworkRequests.contains(networkRequest)) {
-                mNetworkRequests.remove(networkRequest);
-                if (mDataConnection != null) {
-                    // New network request added. Should re-evaluate properties of
-                    // the data connection. For example, the score may change.
-                    mDataConnection.reevaluateDataConnectionProperties();
-                }
-                requestLog(this, "releaseNetwork left with " + mNetworkRequests.size()
-                        + " requests.");
-                if (mNetworkRequests.size() == 0
-                        || type == DcTracker.RELEASE_TYPE_DETACH
-                        || type == DcTracker.RELEASE_TYPE_HANDOVER) {
-                    mDcTracker.disableApn(ApnSetting.getApnTypesBitmaskFromString(mApnType), type);
-                }
-            }
-        }
-    }
-
-    /**
-     * @param excludeDun True if excluding requests that have DUN capability
-     * @return True if the attached network requests contain restricted capability.
-     */
-    public boolean hasRestrictedRequests(boolean excludeDun) {
-        synchronized (mRefCountLock) {
-            for (NetworkRequest nr : mNetworkRequests) {
-                if (excludeDun &&
-                        nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) {
-                    continue;
-                }
-                if (!nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    private final SparseIntArray mRetriesLeftPerErrorCode = new SparseIntArray();
-
-    public void resetErrorCodeRetries() {
-        requestLog(this, "resetErrorCodeRetries");
-
-        String[] config = mPhone.getContext().getResources().getStringArray(
-                com.android.internal.R.array.config_cell_retries_per_error_code);
-        synchronized (mRetriesLeftPerErrorCode) {
-            mRetriesLeftPerErrorCode.clear();
-
-            for (String c : config) {
-                String errorValue[] = c.split(",");
-                if (errorValue != null && errorValue.length == 2) {
-                    int count = 0;
-                    int errorCode = 0;
-                    try {
-                        errorCode = Integer.parseInt(errorValue[0]);
-                        count = Integer.parseInt(errorValue[1]);
-                    } catch (NumberFormatException e) {
-                        log("Exception parsing config_retries_per_error_code: " + e);
-                        continue;
-                    }
-                    if (count > 0 && errorCode > 0) {
-                        mRetriesLeftPerErrorCode.put(errorCode, count);
-                    }
-                } else {
-                    log("Exception parsing config_retries_per_error_code: " + c);
-                }
-            }
-        }
-    }
-
-    public boolean restartOnError(int errorCode) {
-        boolean result = false;
-        int retriesLeft = 0;
-        synchronized(mRetriesLeftPerErrorCode) {
-            retriesLeft = mRetriesLeftPerErrorCode.get(errorCode);
-            switch (retriesLeft) {
-                case 0: {
-                    // not set, never restart modem
-                    break;
-                }
-                case 1: {
-                    resetErrorCodeRetries();
-                    result = true;
-                    break;
-                }
-                default: {
-                    mRetriesLeftPerErrorCode.put(errorCode, retriesLeft - 1);
-                    result = false;
-                }
-            }
-        }
-        requestLog(this, "restartOnError(" + errorCode + ") found " + retriesLeft
-                + " and returned " + result);
-        return result;
-    }
-
-    public int incAndGetConnectionGeneration() {
-        return mConnectionGeneration.incrementAndGet();
-    }
-
-    public int getConnectionGeneration() {
-        return mConnectionGeneration.get();
-    }
-
-    long getRetryAfterDisconnectDelay() {
-        return mRetryManager.getRetryAfterDisconnectDelay();
-    }
-
-    /**
-     * Get APN type from the network request.
-     *
-     * @param nr The network request.
-     * @return The APN type.
-     */
-    public static @ApnType int getApnTypeFromNetworkRequest(NetworkRequest nr) {
-        // For now, ignore the bandwidth stuff
-        if (nr.getTransportTypes().length > 0
-                && !nr.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
-            return ApnSetting.TYPE_NONE;
-        }
-
-        // in the near term just do 1-1 matches.
-        // TODO - actually try to match the set of capabilities
-        int apnType = ApnSetting.TYPE_NONE;
-        boolean error = false;
-
-        if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
-            apnType = ApnSetting.TYPE_DEFAULT;
-        }
-        if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) {
-            if (apnType != ApnSetting.TYPE_NONE) error = true;
-            apnType = ApnSetting.TYPE_MMS;
-        }
-        if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
-            if (apnType != ApnSetting.TYPE_NONE) error = true;
-            apnType = ApnSetting.TYPE_SUPL;
-        }
-        if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) {
-            if (apnType != ApnSetting.TYPE_NONE) error = true;
-            apnType = ApnSetting.TYPE_DUN;
-        }
-        if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOTA)) {
-            if (apnType != ApnSetting.TYPE_NONE) error = true;
-            apnType = ApnSetting.TYPE_FOTA;
-        }
-        if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) {
-            if (apnType != ApnSetting.TYPE_NONE) error = true;
-            apnType = ApnSetting.TYPE_IMS;
-        }
-        if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
-            if (apnType != ApnSetting.TYPE_NONE) error = true;
-            apnType = ApnSetting.TYPE_CBS;
-        }
-        if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_IA)) {
-            if (apnType != ApnSetting.TYPE_NONE) error = true;
-            apnType = ApnSetting.TYPE_IA;
-        }
-        if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_EIMS)) {
-            if (apnType != ApnSetting.TYPE_NONE) error = true;
-            apnType = ApnSetting.TYPE_EMERGENCY;
-        }
-        if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_MCX)) {
-            if (apnType != ApnSetting.TYPE_NONE) error = true;
-            apnType = ApnSetting.TYPE_MCX;
-        }
-        if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_XCAP)) {
-            if (apnType != ApnSetting.TYPE_NONE) error = true;
-            apnType = ApnSetting.TYPE_XCAP;
-        }
-        if (nr.hasCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE)) {
-            if (apnType != ApnSetting.TYPE_NONE) error = true;
-            apnType = ApnSetting.TYPE_ENTERPRISE;
-        }
-        if (error) {
-            // TODO: If this error condition is removed, the framework's handling of
-            // NET_CAPABILITY_NOT_RESTRICTED will need to be updated so requests for
-            // say FOTA and INTERNET are marked as restricted.  This is not how
-            // NetworkCapabilities.maybeMarkCapabilitiesRestricted currently works.
-            Rlog.d(SLOG_TAG, "Multiple apn types specified in request - result is unspecified!");
-        }
-        if (apnType == ApnSetting.TYPE_NONE) {
-            Rlog.d(SLOG_TAG, "Unsupported NetworkRequest in Telephony: nr=" + nr);
-        }
-        return apnType;
-    }
-
-    public List<NetworkRequest> getNetworkRequests() {
-        synchronized (mRefCountLock) {
-            return new ArrayList<NetworkRequest>(mNetworkRequests);
-        }
-    }
-
-    @Override
-    public synchronized String toString() {
-        // We don't print mDataConnection because its recursive.
-        return "{mApnType=" + mApnType + " mState=" + getState() + " mWaitingApns={"
-                    + mRetryManager.getWaitingApns() + " priority=" + mPriority + "}"
-                    + " mApnSetting={" + mApnSetting
-                    + "} mReason=" + mReason + " mDataEnabled=" + mDataEnabled + "}";
-    }
-
-    private void log(String s) {
-        if (DBG) {
-            Rlog.d(LOG_TAG, "[ApnContext:" + mApnType + "] " + s);
-        }
-    }
-
-    public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
-        final IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
-        synchronized (mRefCountLock) {
-            pw.println(toString());
-            if (mNetworkRequests.size() > 0) {
-                pw.println("NetworkRequests:");
-                pw.increaseIndent();
-                for (NetworkRequest nr : mNetworkRequests) {
-                    pw.println(nr);
-                }
-                pw.decreaseIndent();
-            }
-            pw.println("Historical APN state:");
-            pw.increaseIndent();
-            mStateLocalLog.dump(fd, pw, args);
-            pw.decreaseIndent();
-            pw.println(mRetryManager);
-            pw.println("--------------------------");
-        }
-    }
-
-    /** Dumps the ApnContext local log. */
-    public static void dumpLocalLog(FileDescriptor fd, PrintWriter printWriter, String[] args) {
-        printWriter.println("Local log:");
-        synchronized (sLocalLog) {
-            sLocalLog.dump(fd, printWriter, args);
-        }
-    }
-}
diff --git a/src/java/com/android/internal/telephony/dataconnection/ApnSettingUtils.java b/src/java/com/android/internal/telephony/dataconnection/ApnSettingUtils.java
deleted file mode 100644
index 3c2a6ef..0000000
--- a/src/java/com/android/internal/telephony/dataconnection/ApnSettingUtils.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2018 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.dataconnection;
-
-import android.content.Context;
-import android.os.PersistableBundle;
-import android.telephony.Annotation.ApnType;
-import android.telephony.CarrierConfigManager;
-import android.telephony.data.ApnSetting;
-
-import com.android.internal.telephony.Phone;
-import com.android.telephony.Rlog;
-
-import java.util.Arrays;
-import java.util.HashSet;
-
-/**
- * This class represents a apn setting for create PDP link
- */
-public class ApnSettingUtils {
-
-    static final String LOG_TAG = "ApnSetting";
-
-    private static final boolean DBG = false;
-
-    /**
-     * Check if this APN type is metered.
-     *
-     * @param apnType the APN type
-     * @param phone the phone object
-     * @return {@code true} if the APN type is metered, {@code false} otherwise.
-     */
-    public static boolean isMeteredApnType(@ApnType int apnType, Phone phone) {
-        if (phone == null) {
-            return true;
-        }
-
-        boolean isRoaming = phone.getServiceState().getDataRoaming();
-        int subId = phone.getSubId();
-
-        String carrierConfig;
-        // First check if the device is roaming. If yes, use the roaming metered APN list.
-        // Otherwise use the normal metered APN list.
-        if (isRoaming) {
-            carrierConfig = CarrierConfigManager.KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS;
-        } else {
-            carrierConfig = CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS;
-        }
-
-        if (DBG) {
-            Rlog.d(LOG_TAG, "isMeteredApnType: isRoaming=" + isRoaming);
-        }
-
-        CarrierConfigManager configManager = (CarrierConfigManager)
-                phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        if (configManager == null) {
-            Rlog.e(LOG_TAG, "Carrier config service is not available");
-            return true;
-        }
-
-        PersistableBundle b = configManager.getConfigForSubId(subId);
-        if (b == null) {
-            Rlog.e(LOG_TAG, "Can't get the config. subId = " + subId);
-            return true;
-        }
-
-        String[] meteredApnTypes = b.getStringArray(carrierConfig);
-        if (meteredApnTypes == null) {
-            Rlog.e(LOG_TAG, carrierConfig +  " is not available. " + "subId = " + subId);
-            return true;
-        }
-
-        HashSet<String> meteredApnSet = new HashSet<>(Arrays.asList(meteredApnTypes));
-        if (DBG) {
-            Rlog.d(LOG_TAG, "For subId = " + subId + ", metered APN types are "
-                    + Arrays.toString(meteredApnSet.toArray()));
-        }
-
-        if (meteredApnSet.contains(ApnSetting.getApnTypeString(apnType))) {
-            if (DBG) Rlog.d(LOG_TAG, ApnSetting.getApnTypeString(apnType) + " is metered.");
-            return true;
-        } else if (apnType == ApnSetting.TYPE_ALL) {
-            // Assuming no configuration error, if at least one APN type is
-            // metered, then this APN setting is metered.
-            if (meteredApnSet.size() > 0) {
-                if (DBG) Rlog.d(LOG_TAG, "APN_TYPE_ALL APN is metered.");
-                return true;
-            }
-        }
-
-        if (DBG) Rlog.d(LOG_TAG, ApnSetting.getApnTypeString(apnType) + " is not metered.");
-        return false;
-    }
-
-    /**
-     * Check if this APN setting is metered.
-     *
-     * @param apn APN setting
-     * @param phone The phone object
-     * @return True if this APN setting is metered, otherwise false.
-     */
-    public static boolean isMetered(ApnSetting apn, Phone phone) {
-        if (phone == null || apn == null) {
-            return true;
-        }
-
-        for (int apnType : apn.getApnTypes()) {
-            // If one of the APN type is metered, then this APN setting is metered.
-            if (isMeteredApnType(apnType, phone)) {
-                return true;
-            }
-        }
-        return false;
-    }
-}
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
deleted file mode 100644
index 348908a..0000000
--- a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
+++ /dev/null
@@ -1,4088 +0,0 @@
-/*
- * Copyright (C) 2006 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.dataconnection;
-
-import static android.telephony.data.DataCallResponse.PDU_SESSION_ID_NOT_SET;
-
-import static com.android.internal.telephony.dataconnection.DcTracker.REQUEST_TYPE_HANDOVER;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.UserInfo;
-import android.net.ConnectivityManager;
-import android.net.InetAddresses;
-import android.net.KeepalivePacketData;
-import android.net.LinkAddress;
-import android.net.LinkProperties;
-import android.net.NetworkAgentConfig;
-import android.net.NetworkCapabilities;
-import android.net.NetworkFactory;
-import android.net.NetworkProvider;
-import android.net.NetworkRequest;
-import android.net.ProxyInfo;
-import android.net.RouteInfo;
-import android.net.SocketKeepalive;
-import android.net.TelephonyNetworkSpecifier;
-import android.net.vcn.VcnManager;
-import android.net.vcn.VcnManager.VcnNetworkPolicyChangeListener;
-import android.net.vcn.VcnNetworkPolicyResult;
-import android.os.AsyncResult;
-import android.os.HandlerExecutor;
-import android.os.Message;
-import android.os.PersistableBundle;
-import android.os.Process;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.UserManager;
-import android.provider.Telephony;
-import android.telephony.AccessNetworkConstants;
-import android.telephony.AccessNetworkConstants.TransportType;
-import android.telephony.Annotation.ApnType;
-import android.telephony.Annotation.DataFailureCause;
-import android.telephony.Annotation.DataState;
-import android.telephony.Annotation.NetworkType;
-import android.telephony.CarrierConfigManager;
-import android.telephony.DataFailCause;
-import android.telephony.LinkCapacityEstimate;
-import android.telephony.NetworkRegistrationInfo;
-import android.telephony.PreciseDataConnectionState;
-import android.telephony.ServiceState;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.telephony.data.ApnSetting;
-import android.telephony.data.DataCallResponse;
-import android.telephony.data.DataCallResponse.HandoverFailureMode;
-import android.telephony.data.DataProfile;
-import android.telephony.data.DataService;
-import android.telephony.data.DataServiceCallback;
-import android.telephony.data.NetworkSliceInfo;
-import android.telephony.data.Qos;
-import android.telephony.data.QosBearerSession;
-import android.telephony.data.TrafficDescriptor;
-import android.text.TextUtils;
-import android.util.LocalLog;
-import android.util.Pair;
-import android.util.TimeUtils;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.CarrierPrivilegesTracker;
-import com.android.internal.telephony.CarrierSignalAgent;
-import com.android.internal.telephony.DctConstants;
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.PhoneFactory;
-import com.android.internal.telephony.RIL;
-import com.android.internal.telephony.RILConstants;
-import com.android.internal.telephony.RetryManager;
-import com.android.internal.telephony.TelephonyStatsLog;
-import com.android.internal.telephony.data.DataConfigManager;
-import com.android.internal.telephony.data.KeepaliveStatus;
-import com.android.internal.telephony.dataconnection.DcTracker.ReleaseNetworkType;
-import com.android.internal.telephony.dataconnection.DcTracker.RequestNetworkType;
-import com.android.internal.telephony.metrics.DataCallSessionStats;
-import com.android.internal.telephony.metrics.TelephonyMetrics;
-import com.android.internal.telephony.nano.TelephonyProto.RilDataCall;
-import com.android.internal.telephony.uicc.IccUtils;
-import com.android.internal.telephony.util.ArrayUtils;
-import com.android.internal.util.AsyncChannel;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.Protocol;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
-import com.android.net.module.util.NetworkCapabilitiesUtils;
-import com.android.telephony.Rlog;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.UUID;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Consumer;
-
-/**
- * {@hide}
- *
- * DataConnection StateMachine.
- *
- * This a class for representing a single data connection, with instances of this
- * class representing a connection via the cellular network. There may be multiple
- * data connections and all of them are managed by the <code>DataConnectionTracker</code>.
- *
- * NOTE: All DataConnection objects must be running on the same looper, which is the default
- * as the coordinator has members which are used without synchronization.
- */
-public class DataConnection extends StateMachine {
-    private static final boolean DBG = true;
-    private static final boolean VDBG = true;
-
-    private static final String NETWORK_TYPE = "MOBILE";
-
-    private static final String RAT_NAME_5G = "nr";
-    private static final String RAT_NAME_EVDO = "evdo";
-
-    /**
-     * OSId for "Android", using UUID version 5 with namespace ISO OSI.
-     * Prepended to the OsAppId in TrafficDescriptor to use for URSP matching.
-     */
-    private static final UUID OS_ID = UUID.fromString("97a498e3-fc92-5c94-8986-0333d06e4e47");
-
-    /**
-     * The data connection is not being or been handovered. Note this is the state for the source
-     * data connection, not destination data connection
-     */
-    private static final int HANDOVER_STATE_IDLE = 1;
-
-    /**
-     * The data connection is being handovered. Note this is the state for the source
-     * data connection, not destination data connection.
-     */
-    private static final int HANDOVER_STATE_BEING_TRANSFERRED = 2;
-
-    /**
-     * The data connection is already handovered. Note this is the state for the source
-     * data connection, not destination data connection.
-     */
-    private static final int HANDOVER_STATE_COMPLETED = 3;
-
-
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = {"HANDOVER_STATE_"}, value = {
-            HANDOVER_STATE_IDLE,
-            HANDOVER_STATE_BEING_TRANSFERRED,
-            HANDOVER_STATE_COMPLETED})
-    public @interface HandoverState {}
-
-    // The data connection providing default Internet connection will have a higher score of 50.
-    // Other connections will have a slightly lower score of 45. The intention is other connections
-    // will not cause ConnectivityService to tear down default internet connection. For example,
-    // to validate Internet connection on non-default data SIM, we'll set up a temporary Internet
-    // connection on that data SIM. In this case, score of 45 is assigned so ConnectivityService
-    // will not replace the default Internet connection with it.
-    private static final int DEFAULT_INTERNET_CONNECTION_SCORE = 50;
-    private static final int OTHER_CONNECTION_SCORE = 45;
-
-    // The score we report to connectivity service
-    private int mScore;
-
-    // The subscription id associated with this data connection.
-    private int mSubId;
-
-    // The data connection controller
-    private DcController mDcController;
-
-    // The Tester for failing all bringup's
-    private DcTesterFailBringUpAll mDcTesterFailBringUpAll;
-
-    // Whether or not the data connection should allocate its own pdu session id
-    private boolean mDoAllocatePduSessionId;
-
-    private static AtomicInteger mInstanceNumber = new AtomicInteger(0);
-    private AsyncChannel mAc;
-
-    // The DCT that's talking to us, we only support one!
-    private DcTracker mDct = null;
-
-    private String[] mPcscfAddr;
-
-    private final String mTagSuffix;
-
-    private final LocalLog mHandoverLocalLog = new LocalLog(64);
-
-    private int[] mAdministratorUids = new int[0];
-
-    // stats per data call
-    private DataCallSessionStats mDataCallSessionStats;
-
-    /**
-     * Used internally for saving connecting parameters.
-     */
-    public static class ConnectionParams {
-        int mTag;
-        ApnContext mApnContext;
-        int mProfileId;
-        int mRilRat;
-        Message mOnCompletedMsg;
-        final int mConnectionGeneration;
-        @RequestNetworkType
-        final int mRequestType;
-        final int mSubId;
-        final boolean mIsPreferredApn;
-
-        ConnectionParams(ApnContext apnContext, int profileId, int rilRadioTechnology,
-                         Message onCompletedMsg, int connectionGeneration,
-                         @RequestNetworkType int requestType, int subId,
-                         boolean isPreferredApn) {
-            mApnContext = apnContext;
-            mProfileId = profileId;
-            mRilRat = rilRadioTechnology;
-            mOnCompletedMsg = onCompletedMsg;
-            mConnectionGeneration = connectionGeneration;
-            mRequestType = requestType;
-            mSubId = subId;
-            mIsPreferredApn = isPreferredApn;
-        }
-
-        @Override
-        public String toString() {
-            return "{mTag=" + mTag + " mApnContext=" + mApnContext
-                    + " mProfileId=" + mProfileId
-                    + " mRat=" + mRilRat
-                    + " mOnCompletedMsg=" + msgToString(mOnCompletedMsg)
-                    + " mRequestType=" + DcTracker.requestTypeToString(mRequestType)
-                    + " mSubId=" + mSubId
-                    + " mIsPreferredApn=" + mIsPreferredApn
-                    + "}";
-        }
-    }
-
-    /**
-     * Used internally for saving disconnecting parameters.
-     */
-    public static class DisconnectParams {
-        int mTag;
-        public ApnContext mApnContext;
-        String mReason;
-        @ReleaseNetworkType
-        final int mReleaseType;
-        Message mOnCompletedMsg;
-
-        DisconnectParams(ApnContext apnContext, String reason, @ReleaseNetworkType int releaseType,
-                         Message onCompletedMsg) {
-            mApnContext = apnContext;
-            mReason = reason;
-            mReleaseType = releaseType;
-            mOnCompletedMsg = onCompletedMsg;
-        }
-
-        @Override
-        public String toString() {
-            return "{mTag=" + mTag + " mApnContext=" + mApnContext
-                    + " mReason=" + mReason
-                    + " mReleaseType=" + DcTracker.releaseTypeToString(mReleaseType)
-                    + " mOnCompletedMsg=" + msgToString(mOnCompletedMsg) + "}";
-        }
-    }
-
-    private volatile ApnSetting mApnSetting;
-    private ConnectionParams mConnectionParams;
-    private DisconnectParams mDisconnectParams;
-    @DataFailureCause
-    private int mDcFailCause;
-
-    @HandoverFailureMode
-    private int mHandoverFailureMode;
-
-    private Phone mPhone;
-    private DataServiceManager mDataServiceManager;
-    private VcnManager mVcnManager;
-    private final int mTransportType;
-    private LinkProperties mLinkProperties = new LinkProperties();
-    private int mPduSessionId;
-    private long mCreateTime;
-    private long mLastFailTime;
-    @DataFailureCause
-    private int mLastFailCause;
-    private static final String NULL_IP = "0.0.0.0";
-    private Object mUserData;
-    private boolean mCongestedOverride;
-    private boolean mUnmeteredOverride;
-    private int mRilRat = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
-    private int mDataRegState = Integer.MAX_VALUE;
-    // Indicating data connection is suspended due to temporary reasons, for example, out of
-    // service, concurrency voice/data not supported, etc.. Note this flag is only meaningful when
-    // data is in active state. When data is in inactive, connecting, or disconnecting, this flag
-    // is unmeaningful.
-    private boolean mIsSuspended;
-    private int mDownlinkBandwidth = 14;
-    private int mUplinkBandwidth = 14;
-    private Qos mDefaultQos = null;
-    private List<QosBearerSession> mQosBearerSessions = new ArrayList<>();
-    private NetworkSliceInfo mSliceInfo;
-    private List<TrafficDescriptor> mTrafficDescriptors = new ArrayList<>();
-
-    /** The corresponding network agent for this data connection. */
-    private DcNetworkAgent mNetworkAgent;
-
-    /**
-     * The network agent from handover source data connection. This is the potential network agent
-     * that will be transferred here after handover completed.
-     */
-    private DcNetworkAgent mHandoverSourceNetworkAgent;
-
-    private int mDisabledApnTypeBitMask = 0;
-
-    int mTag;
-
-    /** Data connection id assigned by the modem. This is unique across transports */
-    public int mCid;
-
-    @HandoverState
-    private int mHandoverState = HANDOVER_STATE_IDLE;
-    private final Map<ApnContext, ConnectionParams> mApnContexts = new ConcurrentHashMap<>();
-    PendingIntent mReconnectIntent = null;
-
-    /** Class used to track VCN-defined Network policies for this DcNetworkAgent. */
-    private final VcnNetworkPolicyChangeListener mVcnPolicyChangeListener =
-            new DataConnectionVcnNetworkPolicyChangeListener();
-
-    // ***** Event codes for driving the state machine, package visible for Dcc
-    static final int BASE = Protocol.BASE_DATA_CONNECTION;
-    static final int EVENT_CONNECT = BASE + 0;
-    static final int EVENT_SETUP_DATA_CONNECTION_DONE = BASE + 1;
-    static final int EVENT_DEACTIVATE_DONE = BASE + 3;
-    static final int EVENT_DISCONNECT = BASE + 4;
-    static final int EVENT_DISCONNECT_ALL = BASE + 6;
-    static final int EVENT_DATA_STATE_CHANGED = BASE + 7;
-    static final int EVENT_TEAR_DOWN_NOW = BASE + 8;
-    static final int EVENT_LOST_CONNECTION = BASE + 9;
-    static final int EVENT_DATA_CONNECTION_DRS_OR_RAT_CHANGED = BASE + 11;
-    static final int EVENT_DATA_CONNECTION_ROAM_ON = BASE + 12;
-    static final int EVENT_DATA_CONNECTION_ROAM_OFF = BASE + 13;
-    static final int EVENT_BW_REFRESH_RESPONSE = BASE + 14;
-    static final int EVENT_DATA_CONNECTION_VOICE_CALL_STARTED = BASE + 15;
-    static final int EVENT_DATA_CONNECTION_VOICE_CALL_ENDED = BASE + 16;
-    static final int EVENT_DATA_CONNECTION_CONGESTEDNESS_CHANGED = BASE + 17;
-    static final int EVENT_KEEPALIVE_STATUS = BASE + 18;
-    static final int EVENT_KEEPALIVE_STARTED = BASE + 19;
-    static final int EVENT_KEEPALIVE_STOPPED = BASE + 20;
-    static final int EVENT_KEEPALIVE_START_REQUEST = BASE + 21;
-    static final int EVENT_KEEPALIVE_STOP_REQUEST = BASE + 22;
-    static final int EVENT_LINK_CAPACITY_CHANGED = BASE + 23;
-    static final int EVENT_RESET = BASE + 24;
-    static final int EVENT_REEVALUATE_RESTRICTED_STATE = BASE + 25;
-    static final int EVENT_REEVALUATE_DATA_CONNECTION_PROPERTIES = BASE + 26;
-    static final int EVENT_NR_STATE_CHANGED = BASE + 27;
-    static final int EVENT_DATA_CONNECTION_METEREDNESS_CHANGED = BASE + 28;
-    static final int EVENT_NR_FREQUENCY_CHANGED = BASE + 29;
-    static final int EVENT_CARRIER_CONFIG_LINK_BANDWIDTHS_CHANGED = BASE + 30;
-    static final int EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED = BASE + 31;
-    static final int EVENT_CSS_INDICATOR_CHANGED = BASE + 32;
-    static final int EVENT_UPDATE_SUSPENDED_STATE = BASE + 33;
-    static final int EVENT_START_HANDOVER = BASE + 34;
-    static final int EVENT_CANCEL_HANDOVER = BASE + 35;
-    static final int EVENT_START_HANDOVER_ON_TARGET = BASE + 36;
-    static final int EVENT_ALLOCATE_PDU_SESSION_ID = BASE + 37;
-    static final int EVENT_RELEASE_PDU_SESSION_ID = BASE + 38;
-    static final int EVENT_LINK_BANDWIDTH_ESTIMATOR_UPDATE = BASE + 39;
-    private static final int CMD_TO_STRING_COUNT = EVENT_LINK_BANDWIDTH_ESTIMATOR_UPDATE - BASE + 1;
-
-    private static String[] sCmdToString = new String[CMD_TO_STRING_COUNT];
-    static {
-        sCmdToString[EVENT_CONNECT - BASE] = "EVENT_CONNECT";
-        sCmdToString[EVENT_SETUP_DATA_CONNECTION_DONE - BASE] =
-                "EVENT_SETUP_DATA_CONNECTION_DONE";
-        sCmdToString[EVENT_DEACTIVATE_DONE - BASE] = "EVENT_DEACTIVATE_DONE";
-        sCmdToString[EVENT_DISCONNECT - BASE] = "EVENT_DISCONNECT";
-        sCmdToString[EVENT_DISCONNECT_ALL - BASE] = "EVENT_DISCONNECT_ALL";
-        sCmdToString[EVENT_DATA_STATE_CHANGED - BASE] = "EVENT_DATA_STATE_CHANGED";
-        sCmdToString[EVENT_TEAR_DOWN_NOW - BASE] = "EVENT_TEAR_DOWN_NOW";
-        sCmdToString[EVENT_LOST_CONNECTION - BASE] = "EVENT_LOST_CONNECTION";
-        sCmdToString[EVENT_DATA_CONNECTION_DRS_OR_RAT_CHANGED - BASE] =
-                "EVENT_DATA_CONNECTION_DRS_OR_RAT_CHANGED";
-        sCmdToString[EVENT_DATA_CONNECTION_ROAM_ON - BASE] = "EVENT_DATA_CONNECTION_ROAM_ON";
-        sCmdToString[EVENT_DATA_CONNECTION_ROAM_OFF - BASE] = "EVENT_DATA_CONNECTION_ROAM_OFF";
-        sCmdToString[EVENT_BW_REFRESH_RESPONSE - BASE] = "EVENT_BW_REFRESH_RESPONSE";
-        sCmdToString[EVENT_DATA_CONNECTION_VOICE_CALL_STARTED - BASE] =
-                "EVENT_DATA_CONNECTION_VOICE_CALL_STARTED";
-        sCmdToString[EVENT_DATA_CONNECTION_VOICE_CALL_ENDED - BASE] =
-                "EVENT_DATA_CONNECTION_VOICE_CALL_ENDED";
-        sCmdToString[EVENT_DATA_CONNECTION_CONGESTEDNESS_CHANGED - BASE] =
-                "EVENT_DATA_CONNECTION_CONGESTEDNESS_CHANGED";
-        sCmdToString[EVENT_KEEPALIVE_STATUS - BASE] = "EVENT_KEEPALIVE_STATUS";
-        sCmdToString[EVENT_KEEPALIVE_STARTED - BASE] = "EVENT_KEEPALIVE_STARTED";
-        sCmdToString[EVENT_KEEPALIVE_STOPPED - BASE] = "EVENT_KEEPALIVE_STOPPED";
-        sCmdToString[EVENT_KEEPALIVE_START_REQUEST - BASE] = "EVENT_KEEPALIVE_START_REQUEST";
-        sCmdToString[EVENT_KEEPALIVE_STOP_REQUEST - BASE] = "EVENT_KEEPALIVE_STOP_REQUEST";
-        sCmdToString[EVENT_LINK_CAPACITY_CHANGED - BASE] = "EVENT_LINK_CAPACITY_CHANGED";
-        sCmdToString[EVENT_RESET - BASE] = "EVENT_RESET";
-        sCmdToString[EVENT_REEVALUATE_RESTRICTED_STATE - BASE] =
-                "EVENT_REEVALUATE_RESTRICTED_STATE";
-        sCmdToString[EVENT_REEVALUATE_DATA_CONNECTION_PROPERTIES - BASE] =
-                "EVENT_REEVALUATE_DATA_CONNECTION_PROPERTIES";
-        sCmdToString[EVENT_NR_STATE_CHANGED - BASE] = "EVENT_NR_STATE_CHANGED";
-        sCmdToString[EVENT_DATA_CONNECTION_METEREDNESS_CHANGED - BASE] =
-                "EVENT_DATA_CONNECTION_METEREDNESS_CHANGED";
-        sCmdToString[EVENT_NR_FREQUENCY_CHANGED - BASE] = "EVENT_NR_FREQUENCY_CHANGED";
-        sCmdToString[EVENT_CARRIER_CONFIG_LINK_BANDWIDTHS_CHANGED - BASE] =
-                "EVENT_CARRIER_CONFIG_LINK_BANDWIDTHS_CHANGED";
-        sCmdToString[EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED - BASE] =
-                "EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED";
-        sCmdToString[EVENT_CSS_INDICATOR_CHANGED - BASE] = "EVENT_CSS_INDICATOR_CHANGED";
-        sCmdToString[EVENT_UPDATE_SUSPENDED_STATE - BASE] = "EVENT_UPDATE_SUSPENDED_STATE";
-        sCmdToString[EVENT_START_HANDOVER - BASE] = "EVENT_START_HANDOVER";
-        sCmdToString[EVENT_CANCEL_HANDOVER - BASE] = "EVENT_CANCEL_HANDOVER";
-        sCmdToString[EVENT_START_HANDOVER_ON_TARGET - BASE] = "EVENT_START_HANDOVER_ON_TARGET";
-        sCmdToString[EVENT_ALLOCATE_PDU_SESSION_ID - BASE] = "EVENT_ALLOCATE_PDU_SESSION_ID";
-        sCmdToString[EVENT_RELEASE_PDU_SESSION_ID - BASE] = "EVENT_RELEASE_PDU_SESSION_ID";
-        sCmdToString[EVENT_LINK_BANDWIDTH_ESTIMATOR_UPDATE - BASE] =
-                "EVENT_LINK_BANDWIDTH_ESTIMATOR_UPDATE";
-    }
-    // Convert cmd to string or null if unknown
-    static String cmdToString(int cmd) {
-        String value = null;
-        cmd -= BASE;
-        if ((cmd >= 0) && (cmd < sCmdToString.length)) {
-            value = sCmdToString[cmd];
-        }
-        if (value == null) {
-            value = "0x" + Integer.toHexString(cmd + BASE);
-        }
-        return value;
-    }
-
-    /**
-     * Create the connection object
-     *
-     * @param phone the Phone
-     * @param id the connection id
-     * @return DataConnection that was created.
-     */
-    public static DataConnection makeDataConnection(Phone phone, int id, DcTracker dct,
-                                                    DataServiceManager dataServiceManager,
-                                                    DcTesterFailBringUpAll failBringUpAll,
-                                                    DcController dcc) {
-        String transportType = (dataServiceManager.getTransportType()
-                == AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                ? "C"   // Cellular
-                : "I";  // IWLAN
-        DataConnection dc = new DataConnection(phone, transportType + "-"
-                + mInstanceNumber.incrementAndGet(), id, dct, dataServiceManager, failBringUpAll,
-                dcc);
-        dc.start();
-        if (DBG) dc.log("Made " + dc.getName());
-        return dc;
-    }
-
-    void dispose() {
-        log("dispose: call quiteNow()");
-        quitNow();
-    }
-
-    /* Getter functions */
-
-    LinkProperties getLinkProperties() {
-        return new LinkProperties(mLinkProperties);
-    }
-
-    boolean isDisconnecting() {
-        return getCurrentState() == mDisconnectingState
-                || getCurrentState() == mDisconnectingErrorCreatingConnection;
-    }
-
-    @VisibleForTesting
-    public boolean isActive() {
-        return getCurrentState() == mActiveState;
-    }
-
-    @VisibleForTesting
-    public boolean isInactive() {
-        return getCurrentState() == mInactiveState;
-    }
-
-    boolean isActivating() {
-        return getCurrentState() == mActivatingState;
-    }
-
-    boolean hasBeenTransferred() {
-        return mHandoverState == HANDOVER_STATE_COMPLETED;
-    }
-
-    int getCid() {
-        return mCid;
-    }
-
-    /**
-     * @return DataConnection's ApnSetting.
-     */
-    public ApnSetting getApnSetting() {
-        return mApnSetting;
-    }
-
-    /**
-     * Update http proxy of link properties based on current apn setting
-     */
-    private void updateLinkPropertiesHttpProxy() {
-        if (mApnSetting == null
-                || TextUtils.isEmpty(mApnSetting.getProxyAddressAsString())) {
-            return;
-        }
-        try {
-            int port = mApnSetting.getProxyPort();
-            if (port == -1) {
-                port = 8080;
-            }
-            ProxyInfo proxy = ProxyInfo.buildDirectProxy(
-                    mApnSetting.getProxyAddressAsString(), port);
-            mLinkProperties.setHttpProxy(proxy);
-        } catch (NumberFormatException e) {
-            loge("onDataSetupComplete: NumberFormatException making ProxyProperties ("
-                    + mApnSetting.getProxyPort() + "): " + e);
-        }
-    }
-
-    public static class UpdateLinkPropertyResult {
-        public SetupResult setupResult = SetupResult.SUCCESS;
-        public LinkProperties oldLp;
-        public LinkProperties newLp;
-        public UpdateLinkPropertyResult(LinkProperties curLp) {
-            oldLp = curLp;
-            newLp = curLp;
-        }
-    }
-
-    /**
-     * Class returned by onSetupConnectionCompleted.
-     */
-    public enum SetupResult {
-        SUCCESS,
-        ERROR_RADIO_NOT_AVAILABLE,
-        ERROR_INVALID_ARG,
-        ERROR_STALE,
-        ERROR_DATA_SERVICE_SPECIFIC_ERROR,
-        ERROR_DUPLICATE_CID,
-        ERROR_NO_DEFAULT_CONNECTION;
-
-        public int mFailCause;
-
-        SetupResult() {
-            mFailCause = DataFailCause.getFailCause(0);
-        }
-
-        @Override
-        public String toString() {
-            return name() + "  SetupResult.mFailCause=" + DataFailCause.toString(mFailCause);
-        }
-    }
-
-    public boolean isIpv4Connected() {
-        boolean ret = false;
-        Collection <InetAddress> addresses = mLinkProperties.getAddresses();
-
-        for (InetAddress addr: addresses) {
-            if (addr instanceof java.net.Inet4Address) {
-                java.net.Inet4Address i4addr = (java.net.Inet4Address) addr;
-                if (!i4addr.isAnyLocalAddress() && !i4addr.isLinkLocalAddress() &&
-                        !i4addr.isLoopbackAddress() && !i4addr.isMulticastAddress()) {
-                    ret = true;
-                    break;
-                }
-            }
-        }
-        return ret;
-    }
-
-    public boolean isIpv6Connected() {
-        boolean ret = false;
-        Collection <InetAddress> addresses = mLinkProperties.getAddresses();
-
-        for (InetAddress addr: addresses) {
-            if (addr instanceof java.net.Inet6Address) {
-                java.net.Inet6Address i6addr = (java.net.Inet6Address) addr;
-                if (!i6addr.isAnyLocalAddress() && !i6addr.isLinkLocalAddress() &&
-                        !i6addr.isLoopbackAddress() && !i6addr.isMulticastAddress()) {
-                    ret = true;
-                    break;
-                }
-            }
-        }
-        return ret;
-    }
-
-    public int getPduSessionId() {
-        return mPduSessionId;
-    }
-
-    public NetworkSliceInfo getSliceInfo() {
-        return mSliceInfo;
-    }
-
-    public List<TrafficDescriptor> getTrafficDescriptors() {
-        return mTrafficDescriptors;
-    }
-
-    /**
-     * Update DC fields based on a new DataCallResponse
-     * @param response the response to use to update DC fields
-     */
-    public void updateResponseFields(DataCallResponse response) {
-        updateQosParameters(response);
-        updateSliceInfo(response);
-        updateTrafficDescriptors(response);
-    }
-
-    public void updateQosParameters(final @Nullable DataCallResponse response) {
-        if (response == null) {
-            mDefaultQos = null;
-            mQosBearerSessions.clear();
-            return;
-        }
-
-        mDefaultQos = response.getDefaultQos();
-        mQosBearerSessions = response.getQosBearerSessions();
-
-        if (mNetworkAgent != null) {
-            syncQosToNetworkAgent();
-        }
-    }
-
-    private void syncQosToNetworkAgent() {
-        final DcNetworkAgent networkAgent = mNetworkAgent;
-        final List<QosBearerSession> qosBearerSessions = mQosBearerSessions;
-        if (qosBearerSessions == null) {
-            networkAgent.updateQosBearerSessions(new ArrayList<>());
-            return;
-        }
-        networkAgent.updateQosBearerSessions(qosBearerSessions);
-    }
-
-    /**
-     * Update the latest slice info on this data connection with
-     * {@link DataCallResponse#getSliceInfo}.
-     */
-    public void updateSliceInfo(DataCallResponse response) {
-        mSliceInfo = response.getSliceInfo();
-    }
-
-    /**
-     * Update the latest traffic descriptor on this data connection with
-     * {@link DataCallResponse#getTrafficDescriptors}.
-     */
-    public void updateTrafficDescriptors(DataCallResponse response) {
-        mTrafficDescriptors = response.getTrafficDescriptors();
-        mDcController.updateTrafficDescriptorsForCid(response.getId(),
-                response.getTrafficDescriptors());
-    }
-
-    @VisibleForTesting
-    public UpdateLinkPropertyResult updateLinkProperty(DataCallResponse newState) {
-        UpdateLinkPropertyResult result = new UpdateLinkPropertyResult(mLinkProperties);
-
-        if (newState == null) return result;
-
-        result.newLp = new LinkProperties();
-
-        // set link properties based on data call response
-        result.setupResult = setLinkProperties(newState, result.newLp);
-        if (result.setupResult != SetupResult.SUCCESS) {
-            if (DBG) log("updateLinkProperty failed : " + result.setupResult);
-            return result;
-        }
-        // copy HTTP proxy as it is not part DataCallResponse.
-        result.newLp.setHttpProxy(mLinkProperties.getHttpProxy());
-
-        checkSetMtu(mApnSetting, result.newLp);
-
-        mLinkProperties = result.newLp;
-
-        updateTcpBufferSizes(mRilRat);
-
-        if (DBG && (! result.oldLp.equals(result.newLp))) {
-            log("updateLinkProperty old LP=" + result.oldLp);
-            log("updateLinkProperty new LP=" + result.newLp);
-        }
-
-        if (result.newLp.equals(result.oldLp) == false &&
-                mNetworkAgent != null) {
-            mNetworkAgent.sendLinkProperties(mLinkProperties, DataConnection.this);
-        }
-
-        return result;
-    }
-
-    /**
-     * Sets the pdu session id of the data connection
-     * @param pduSessionId pdu session id to set
-     */
-    @VisibleForTesting
-    public void setPduSessionId(int pduSessionId) {
-        if (mPduSessionId != pduSessionId) {
-            logd("Changing pdu session id from: " + mPduSessionId + " to: " + pduSessionId + ", "
-                    + "Handover state: " + handoverStateToString(this.mHandoverState));
-            mPduSessionId = pduSessionId;
-        }
-    }
-
-    /**
-     * Read the MTU value from link properties where it can be set from network. In case
-     * not set by the network, set it again using the mtu szie value defined in the APN
-     * database for the connected APN
-     */
-    private void checkSetMtu(ApnSetting apn, LinkProperties lp) {
-        if (lp == null) return;
-
-        if (apn == null || lp == null) return;
-
-        if (lp.getMtu() != PhoneConstants.UNSET_MTU) {
-            if (DBG) log("MTU set by call response to: " + lp.getMtu());
-            return;
-        }
-
-        if (apn != null && apn.getMtuV4() != PhoneConstants.UNSET_MTU) {
-            lp.setMtu(apn.getMtuV4());
-            if (DBG) log("MTU set by APN to: " + apn.getMtuV4());
-            return;
-        }
-
-        int mtu = mPhone.getContext().getResources().getInteger(
-                com.android.internal.R.integer.config_mobile_mtu);
-        if (mtu != PhoneConstants.UNSET_MTU) {
-            lp.setMtu(mtu);
-            if (DBG) log("MTU set by config resource to: " + mtu);
-        }
-    }
-
-    //***** Constructor (NOTE: uses dcc.getHandler() as its Handler)
-    private DataConnection(Phone phone, String tagSuffix, int id,
-                           DcTracker dct, DataServiceManager dataServiceManager,
-                           DcTesterFailBringUpAll failBringUpAll, DcController dcc) {
-        super("DC-" + tagSuffix, dcc);
-        mTagSuffix = tagSuffix;
-        setLogRecSize(300);
-        setLogOnlyTransitions(true);
-        if (DBG) log("DataConnection created");
-
-        mPhone = phone;
-        mDct = dct;
-        mDataServiceManager = dataServiceManager;
-        mVcnManager = mPhone.getContext().getSystemService(VcnManager.class);
-        mTransportType = dataServiceManager.getTransportType();
-        mDcTesterFailBringUpAll = failBringUpAll;
-        mDcController = dcc;
-        mId = id;
-        mCid = -1;
-        mDataRegState = mPhone.getServiceState().getDataRegistrationState();
-        mIsSuspended = false;
-        mDataCallSessionStats = new DataCallSessionStats(mPhone);
-        mDoAllocatePduSessionId = false;
-
-        int networkType = getNetworkType();
-        mRilRat = ServiceState.networkTypeToRilRadioTechnology(networkType);
-        updateLinkBandwidthsFromCarrierConfig(mRilRat);
-
-        addState(mDefaultState);
-            addState(mInactiveState, mDefaultState);
-            addState(mActivatingState, mDefaultState);
-            addState(mActiveState, mDefaultState);
-            addState(mDisconnectingState, mDefaultState);
-            addState(mDisconnectingErrorCreatingConnection, mDefaultState);
-        setInitialState(mInactiveState);
-    }
-
-    private @NetworkType int getNetworkType() {
-        ServiceState ss = mPhone.getServiceState();
-        int networkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
-
-        NetworkRegistrationInfo nri = ss.getNetworkRegistrationInfo(
-                NetworkRegistrationInfo.DOMAIN_PS, mTransportType);
-        if (nri != null) {
-            networkType = nri.getAccessNetworkTechnology();
-        }
-
-        return networkType;
-    }
-
-    /**
-     * Get the source transport for handover. For example, handover from WWAN to WLAN, WWAN is the
-     * source transport, and vice versa.
-     */
-    private @TransportType int getHandoverSourceTransport() {
-        return mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN
-                ? AccessNetworkConstants.TRANSPORT_TYPE_WLAN
-                : AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
-    }
-
-    /**
-     * API to generate the OsAppId for enterprise traffic category.
-     * @return byte[] representing OsId + length of OsAppId + OsAppId
-     */
-    @VisibleForTesting
-    public static byte[] getEnterpriseOsAppId() {
-        byte[] osAppId = NetworkCapabilities.getCapabilityCarrierName(
-                NetworkCapabilities.NET_CAPABILITY_ENTERPRISE).getBytes();
-        // 16 bytes for UUID, 1 byte for length of osAppId, and up to 255 bytes for osAppId
-        ByteBuffer bb = ByteBuffer.allocate(16 + 1 + osAppId.length);
-        bb.putLong(OS_ID.getMostSignificantBits());
-        bb.putLong(OS_ID.getLeastSignificantBits());
-        bb.put((byte) osAppId.length);
-        bb.put(osAppId);
-        if (VDBG) {
-            Rlog.d("DataConnection", "getEnterpriseOsAppId: "
-                    + IccUtils.bytesToHexString(bb.array()));
-        }
-        return bb.array();
-    }
-
-    /**
-     * Begin setting up a data connection, calls setupDataCall
-     * and the ConnectionParams will be returned with the
-     * EVENT_SETUP_DATA_CONNECTION_DONE
-     *
-     * @param cp is the connection parameters
-     *
-     * @return Fail cause if failed to setup data connection. {@link DataFailCause#NONE} if success.
-     */
-    private @DataFailureCause int connect(ConnectionParams cp) {
-        log("connect: carrier='" + mApnSetting.getEntryName()
-                + "' APN='" + mApnSetting.getApnName()
-                + "' proxy='" + mApnSetting.getProxyAddressAsString()
-                + "' port='" + mApnSetting.getProxyPort() + "'");
-        ApnContext.requestLog(cp.mApnContext, "DataConnection.connect");
-
-        // Check if we should fake an error.
-        if (mDcTesterFailBringUpAll.getDcFailBringUp().mCounter  > 0) {
-            DataCallResponse response = new DataCallResponse.Builder()
-                    .setCause(mDcTesterFailBringUpAll.getDcFailBringUp().mFailCause)
-                    .setRetryDurationMillis(
-                            mDcTesterFailBringUpAll.getDcFailBringUp().mSuggestedRetryTime)
-                    .setMtuV4(PhoneConstants.UNSET_MTU)
-                    .setMtuV6(PhoneConstants.UNSET_MTU)
-                    .build();
-
-            Message msg = obtainMessage(EVENT_SETUP_DATA_CONNECTION_DONE, cp);
-            AsyncResult.forMessage(msg, response, null);
-            sendMessage(msg);
-            if (DBG) {
-                log("connect: FailBringUpAll=" + mDcTesterFailBringUpAll.getDcFailBringUp()
-                        + " send error response=" + response);
-            }
-            mDcTesterFailBringUpAll.getDcFailBringUp().mCounter -= 1;
-            return DataFailCause.NONE;
-        }
-
-        mCreateTime = -1;
-        mLastFailTime = -1;
-        mLastFailCause = DataFailCause.NONE;
-
-        Message msg = obtainMessage(EVENT_SETUP_DATA_CONNECTION_DONE, cp);
-        msg.obj = cp;
-
-        DataProfile dp = new DataProfile.Builder()
-                .setApnSetting(mApnSetting)
-                .setPreferred(cp.mIsPreferredApn)
-                .build();
-
-        // We need to use the actual modem roaming state instead of the framework roaming state
-        // here. This flag is only passed down to ril_service for picking the correct protocol (for
-        // old modem backward compatibility).
-        boolean isModemRoaming = mPhone.getServiceState().getDataRoamingFromRegistration();
-
-        // If the apn is NOT metered, we will allow data roaming regardless of the setting.
-        boolean isUnmeteredApnType = !ApnSettingUtils.isMeteredApnType(
-                cp.mApnContext.getApnTypeBitmask(), mPhone);
-
-        // Set this flag to true if the user turns on data roaming. Or if we override the roaming
-        // state in framework, we should set this flag to true as well so the modem will not reject
-        // the data call setup (because the modem actually thinks the device is roaming).
-        boolean allowRoaming = mPhone.getDataRoamingEnabled()
-                || (isModemRoaming && (!mPhone.getServiceState().getDataRoaming()
-                || isUnmeteredApnType));
-
-        String dnn = null;
-        byte[] osAppId = null;
-        if (cp.mApnContext.getApnTypeBitmask() == ApnSetting.TYPE_ENTERPRISE) {
-            osAppId = getEnterpriseOsAppId();
-        } else {
-            dnn = mApnSetting.getApnName();
-        }
-        final TrafficDescriptor td = osAppId == null && dnn == null ? null
-                : new TrafficDescriptor(dnn, osAppId);
-        final boolean matchAllRuleAllowed = td == null || td.getOsAppId() == null;
-
-        if (DBG) {
-            log("allowRoaming=" + allowRoaming
-                    + ", mPhone.getDataRoamingEnabled()=" + mPhone.getDataRoamingEnabled()
-                    + ", isModemRoaming=" + isModemRoaming
-                    + ", mPhone.getServiceState().getDataRoaming()="
-                    + mPhone.getServiceState().getDataRoaming()
-                    + ", isUnmeteredApnType=" + isUnmeteredApnType
-                    + ", trafficDescriptor=" + td
-                    + ", matchAllRuleAllowed=" + matchAllRuleAllowed
-            );
-        }
-
-        // Check if this data setup is a handover.
-        LinkProperties linkProperties = null;
-        int reason = DataService.REQUEST_REASON_NORMAL;
-        if (cp.mRequestType == REQUEST_TYPE_HANDOVER) {
-            // If this is a data setup for handover, we need to pass the link properties
-            // of the existing data connection to the modem.
-            DcTracker srcDcTracker = mPhone.getDcTracker(getHandoverSourceTransport());
-            if (srcDcTracker == null || cp.mApnContext == null) {
-                loge("connect: Handover failed. dcTracker=" + srcDcTracker + ", apnContext="
-                        + cp.mApnContext);
-                return DataFailCause.HANDOVER_FAILED;
-            }
-
-
-            // srcDc is the source data connection while the current instance is the target
-            DataConnection srcDc =
-                    srcDcTracker.getDataConnectionByApnType(cp.mApnContext.getApnType());
-            if (srcDc == null) {
-                loge("connect: Can't find data connection for handover.");
-                return DataFailCause.HANDOVER_FAILED;
-            }
-
-            // Helpful for logging purposes
-            DataServiceManager srcDsm = srcDc.mDataServiceManager;
-            String srcDsmTag = (srcDsm == null ? "(null)" : srcDsm.getTag());
-            logd("connect: REQUEST_TYPE_HANDOVER - Request handover from " + srcDc.getName()
-                    + ", targetDsm=" + mDataServiceManager.getTag()
-                    + ", sourceDsm=" + srcDsmTag);
-
-
-            /* startHandover is called on the source data connection, and if successful,
-               we ask the target data connection (which is the current instance) to call
-               #setupDataCall with request type handover.
-            */
-            Consumer<Integer> onCompleted = (dataServiceCallbackResultCode) ->
-                    /* startHandover is called on the srcDc handler, but the callback needs to
-                       be called on the current (which is the targetDc) handler which is why we
-                       call sendRunnableMessage. */
-                    sendRunnableMessage(EVENT_START_HANDOVER_ON_TARGET,
-                        (inCorrectState) -> requestHandover(inCorrectState, srcDc,
-                            dataServiceCallbackResultCode,
-                            cp, msg, dp, isModemRoaming, allowRoaming));
-            srcDc.startHandover(onCompleted);
-            return DataFailCause.NONE;
-        }
-
-        // setup data call for REQUEST_TYPE_NORMAL
-        mDoAllocatePduSessionId = mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN;
-        allocatePduSessionId(psi -> {
-            this.setPduSessionId(psi);
-            mDataServiceManager.setupDataCall(
-                    ServiceState.rilRadioTechnologyToAccessNetworkType(cp.mRilRat),
-                    dp,
-                    isModemRoaming,
-                    allowRoaming,
-                    reason,
-                    linkProperties,
-                    psi,
-                    null, //slice info is null since this is not a handover
-                    td,
-                    matchAllRuleAllowed,
-                    msg);
-            TelephonyMetrics.getInstance().writeSetupDataCall(mPhone.getPhoneId(), cp.mRilRat,
-                    dp.getProfileId(), dp.getApn(), dp.getProtocolType());
-        });
-        return DataFailCause.NONE;
-    }
-
-    private void allocatePduSessionId(Consumer<Integer> allocateCallback) {
-        if (mDoAllocatePduSessionId) {
-            Message msg = this.obtainMessage(EVENT_ALLOCATE_PDU_SESSION_ID);
-            msg.obj = allocateCallback;
-            mPhone.mCi.allocatePduSessionId(msg);
-        } else {
-            allocateCallback.accept(PDU_SESSION_ID_NOT_SET);
-        }
-    }
-
-    private void onRquestHandoverFailed(ConnectionParams cp) {
-        sendMessage(obtainMessage(EVENT_CANCEL_HANDOVER));
-        notifyConnectCompleted(cp, DataFailCause.UNKNOWN,
-                DataCallResponse.HANDOVER_FAILURE_MODE_UNKNOWN, false);
-    }
-
-    private void requestHandover(boolean inCorrectState, DataConnection srcDc,
-            @DataServiceCallback.ResultCode int resultCode,
-            ConnectionParams cp, Message msg, DataProfile dp, boolean isModemRoaming,
-            boolean allowRoaming) {
-
-        if (!inCorrectState) {
-            logd("requestHandover: Not in correct state");
-            if (isResultCodeSuccess(resultCode)) {
-                if (srcDc != null) {
-                    logd("requestHandover: Not in correct state - Success result code");
-                    // We need to cancel the handover on source if we ended up in the wrong state.
-                    srcDc.cancelHandover();
-                } else {
-                    logd("requestHandover: Not in correct state - Success result code - "
-                            + "srcdc = null");
-                }
-            }
-            onRquestHandoverFailed(cp);
-            return;
-        } else if (!isResultCodeSuccess(resultCode)) {
-            if (DBG) {
-                logd("requestHandover: Non success result code from DataService, "
-                        + "setupDataCall will not be called, result code = "
-                        + DataServiceCallback.resultCodeToString(resultCode));
-            }
-            onRquestHandoverFailed(cp);
-            return;
-        }
-
-        if (srcDc == null) {
-            loge("requestHandover: Cannot find source data connection.");
-            onRquestHandoverFailed(cp);
-            return;
-        }
-
-        LinkProperties linkProperties;
-        int reason;
-
-        // Preserve the potential network agent from the source data connection. The ownership
-        // is not transferred at this moment.
-        mHandoverSourceNetworkAgent = srcDc.getNetworkAgent();
-        if (mHandoverSourceNetworkAgent == null) {
-            loge("requestHandover: Cannot get network agent from the source dc " + srcDc.getName());
-            onRquestHandoverFailed(cp);
-            return;
-        }
-
-        linkProperties = srcDc.getLinkProperties();
-        if (linkProperties == null || linkProperties.getLinkAddresses().isEmpty()) {
-            loge("requestHandover: Can't find link properties of handover data connection. dc="
-                    + srcDc);
-            onRquestHandoverFailed(cp);
-            return;
-        }
-
-        mHandoverLocalLog.log("Handover started. Preserved the agent.");
-        log("Get the handover source network agent: " + mHandoverSourceNetworkAgent);
-
-        reason = DataService.REQUEST_REASON_HANDOVER;
-
-        TrafficDescriptor td = dp.getApn() == null ? null
-                : new TrafficDescriptor(dp.getApn(), null);
-        boolean matchAllRuleAllowed = true;
-
-        mDataServiceManager.setupDataCall(
-                ServiceState.rilRadioTechnologyToAccessNetworkType(cp.mRilRat),
-                dp,
-                isModemRoaming,
-                allowRoaming,
-                reason,
-                linkProperties,
-                srcDc.getPduSessionId(),
-                srcDc.getSliceInfo(),
-                td,
-                matchAllRuleAllowed,
-                msg);
-        TelephonyMetrics.getInstance().writeSetupDataCall(mPhone.getPhoneId(), cp.mRilRat,
-                dp.getProfileId(), dp.getApn(), dp.getProtocolType());
-    }
-
-    /**
-     * Called on the source data connection from the target data connection.
-     */
-    @VisibleForTesting
-    public void startHandover(Consumer<Integer> onTargetDcComplete) {
-        logd("startHandover: " + toStringSimple());
-        // Set the handover state to being transferred on "this" data connection which is the src.
-        setHandoverState(HANDOVER_STATE_BEING_TRANSFERRED);
-
-        Consumer<Integer> onSrcDcComplete =
-                resultCode -> onHandoverStarted(resultCode, onTargetDcComplete);
-        /*
-            The flow here is:
-            srcDc#startHandover -> dataService#startHandover -> (onHandoverStarted) ->
-                onSrcDcComplete -> onTargetDcComplete
-         */
-        mDataServiceManager.startHandover(mCid,
-                this.obtainMessage(EVENT_START_HANDOVER,
-                        onSrcDcComplete));
-    }
-
-    /**
-     * Called on the source data connection when the async call to start handover is complete
-     */
-    private void onHandoverStarted(@DataServiceCallback.ResultCode int resultCode,
-            Consumer<Integer> onTargetDcComplete) {
-        logd("onHandoverStarted: " + toStringSimple());
-        if (!isResultCodeSuccess(resultCode)) {
-            setHandoverState(HANDOVER_STATE_IDLE);
-        }
-        onTargetDcComplete.accept(resultCode);
-    }
-
-    private void cancelHandover() {
-        if (mHandoverState != HANDOVER_STATE_BEING_TRANSFERRED) {
-            logd("cancelHandover: handover state is " + handoverStateToString(mHandoverState)
-                    + ", expecting HANDOVER_STATE_BEING_TRANSFERRED");
-        }
-        mDataServiceManager.cancelHandover(mCid, this.obtainMessage(EVENT_CANCEL_HANDOVER));
-        setHandoverState(HANDOVER_STATE_IDLE);
-    }
-
-    /**
-     * Update NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED based on congested override
-     * @param isCongested whether this DC should be set to congested or not
-     */
-    public void onCongestednessChanged(boolean isCongested) {
-        sendMessage(obtainMessage(EVENT_DATA_CONNECTION_CONGESTEDNESS_CHANGED, isCongested));
-    }
-
-    /**
-     * Update NetworkCapabilities.NET_CAPABILITY_NOT_METERED based on metered override
-     * @param isUnmetered whether this DC should be set to unmetered or not
-     */
-    public void onMeterednessChanged(boolean isUnmetered) {
-        sendMessage(obtainMessage(EVENT_DATA_CONNECTION_METEREDNESS_CHANGED, isUnmetered));
-    }
-
-    /**
-     * TearDown the data connection when the deactivation is complete a Message with
-     * msg.what == EVENT_DEACTIVATE_DONE
-     *
-     * @param o is the object returned in the AsyncResult.obj.
-     */
-    private void tearDownData(Object o) {
-        int discReason = DataService.REQUEST_REASON_NORMAL;
-        ApnContext apnContext = null;
-        if ((o != null) && (o instanceof DisconnectParams)) {
-            DisconnectParams dp = (DisconnectParams) o;
-            apnContext = dp.mApnContext;
-            if (TextUtils.equals(dp.mReason, Phone.REASON_RADIO_TURNED_OFF)
-                    || TextUtils.equals(dp.mReason, Phone.REASON_PDP_RESET)) {
-                discReason = DataService.REQUEST_REASON_SHUTDOWN;
-            } else if (dp.mReleaseType == DcTracker.RELEASE_TYPE_HANDOVER) {
-                discReason = DataService.REQUEST_REASON_HANDOVER;
-            }
-        }
-
-        String str = "tearDownData. mCid=" + mCid + ", reason=" + discReason;
-        if (DBG) log(str);
-        ApnContext.requestLog(apnContext, str);
-
-
-        //Needed to be final to work in a closure
-        final int fDiscReason = discReason;
-        releasePduSessionId(() -> {
-            // This is run after release pdu session id is complete
-            this.setPduSessionId(PDU_SESSION_ID_NOT_SET);
-            mDataServiceManager.deactivateDataCall(mCid, fDiscReason,
-                    obtainMessage(EVENT_DEACTIVATE_DONE, mTag, 0, o));
-            mDataCallSessionStats.setDeactivateDataCallReason(fDiscReason);
-        });
-    }
-
-    private void releasePduSessionId(Runnable releaseCallback) {
-        // If the transport is IWLAN, and there is a valid PDU session id, also the data connection
-        // is not being handovered, we should release the pdu session id.
-        if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN
-                && mHandoverState == HANDOVER_STATE_IDLE
-                && this.getPduSessionId() != PDU_SESSION_ID_NOT_SET) {
-            Message msg = this.obtainMessage(EVENT_RELEASE_PDU_SESSION_ID);
-            msg.obj = releaseCallback;
-            mPhone.mCi.releasePduSessionId(msg, this.getPduSessionId());
-        } else {
-            // Just go and run the callback since we either have no pdu session id to release
-            // or we are in the middle of a handover
-            releaseCallback.run();
-        }
-    }
-
-    private void notifyAllWithEvent(ApnContext alreadySent, int event, String reason) {
-        for (ConnectionParams cp : mApnContexts.values()) {
-            ApnContext apnContext = cp.mApnContext;
-            if (apnContext == alreadySent) continue;
-            if (reason != null) apnContext.setReason(reason);
-            Pair<ApnContext, Integer> pair = new Pair<>(apnContext, cp.mConnectionGeneration);
-            Message msg = mDct.obtainMessage(event, cp.mRequestType,
-                    DataCallResponse.HANDOVER_FAILURE_MODE_UNKNOWN, pair);
-            AsyncResult.forMessage(msg);
-            msg.sendToTarget();
-        }
-    }
-
-    /**
-     * Send the connectionCompletedMsg.
-     *
-     * @param cp is the ConnectionParams
-     * @param cause and if no error the cause is DataFailCause.NONE
-     * @param handoverFailureMode The action on handover failure
-     * @param sendAll is true if all contexts are to be notified
-     */
-    private void notifyConnectCompleted(ConnectionParams cp, @DataFailureCause int cause,
-            @HandoverFailureMode int handoverFailureMode, boolean sendAll) {
-        ApnContext alreadySent = null;
-
-        if (cp != null && cp.mOnCompletedMsg != null) {
-            // Get the completed message but only use it once
-            Message connectionCompletedMsg = cp.mOnCompletedMsg;
-            cp.mOnCompletedMsg = null;
-            alreadySent = cp.mApnContext;
-
-            long timeStamp = System.currentTimeMillis();
-            connectionCompletedMsg.arg1 = cp.mRequestType;
-            connectionCompletedMsg.arg2 = handoverFailureMode;
-
-            if (cause == DataFailCause.NONE) {
-                mCreateTime = timeStamp;
-                AsyncResult.forMessage(connectionCompletedMsg);
-            } else {
-                mLastFailCause = cause;
-                mLastFailTime = timeStamp;
-
-                // Return message with a Throwable exception to signify an error.
-                if (cause == DataFailCause.NONE) cause = DataFailCause.UNKNOWN;
-                AsyncResult.forMessage(connectionCompletedMsg, cause,
-                        new Throwable(DataFailCause.toString(cause)));
-            }
-            if (DBG) {
-                log("notifyConnectCompleted at " + timeStamp + " cause="
-                        + DataFailCause.toString(cause) + " connectionCompletedMsg="
-                        + msgToString(connectionCompletedMsg));
-            }
-
-            connectionCompletedMsg.sendToTarget();
-        }
-        if (sendAll) {
-            log("Send to all. " + alreadySent + " " + DataFailCause.toString(cause));
-            notifyAllWithEvent(alreadySent, DctConstants.EVENT_DATA_SETUP_COMPLETE_ERROR,
-                    DataFailCause.toString(cause));
-        }
-    }
-
-    /**
-     * Send ar.userObj if its a message, which is should be back to originator.
-     *
-     * @param dp is the DisconnectParams.
-     */
-    private void notifyDisconnectCompleted(DisconnectParams dp, boolean sendAll) {
-        if (VDBG) log("NotifyDisconnectCompleted");
-
-        ApnContext alreadySent = null;
-        String reason = null;
-
-        if (dp != null && dp.mOnCompletedMsg != null) {
-            // Get the completed message but only use it once
-            Message msg = dp.mOnCompletedMsg;
-            dp.mOnCompletedMsg = null;
-            if (msg.obj instanceof ApnContext) {
-                alreadySent = (ApnContext)msg.obj;
-            }
-            reason = dp.mReason;
-            if (VDBG) {
-                log(String.format("msg=%s msg.obj=%s", msg.toString(),
-                    ((msg.obj instanceof String) ? (String) msg.obj : "<no-reason>")));
-            }
-            AsyncResult.forMessage(msg);
-            msg.sendToTarget();
-        }
-        if (sendAll) {
-            if (reason == null) {
-                reason = DataFailCause.toString(DataFailCause.UNKNOWN);
-            }
-            notifyAllWithEvent(alreadySent, DctConstants.EVENT_DISCONNECT_DONE, reason);
-        }
-        if (DBG) log("NotifyDisconnectCompleted DisconnectParams=" + dp);
-    }
-
-    private void sendRunnableMessage(int eventCode, @NonNull final Consumer<Boolean> r) {
-        sendMessage(eventCode, r);
-    }
-
-    /*
-     * **************************************************************************
-     * Begin Members and methods owned by DataConnectionTracker but stored
-     * in a DataConnection because there is one per connection.
-     * **************************************************************************
-     */
-
-    /*
-     * The id is owned by DataConnectionTracker.
-     */
-    private int mId;
-
-    /**
-     * Get the DataConnection ID
-     */
-    public int getDataConnectionId() {
-        return mId;
-    }
-
-    /*
-     * **************************************************************************
-     * End members owned by DataConnectionTracker
-     * **************************************************************************
-     */
-
-    /**
-     * Clear all settings called when entering mInactiveState.
-     */
-    private synchronized void clearSettings() {
-        if (DBG) log("clearSettings");
-
-        mCreateTime = -1;
-        mLastFailTime = -1;
-        mLastFailCause = DataFailCause.NONE;
-        mCid = -1;
-
-        mPcscfAddr = new String[5];
-
-        mLinkProperties = new LinkProperties();
-        mApnContexts.clear();
-        mApnSetting = null;
-        mUnmeteredUseOnly = false;
-        mMmsUseOnly = false;
-        mEnterpriseUse = false;
-        mRestrictedNetworkOverride = false;
-        mDcFailCause = DataFailCause.NONE;
-        mDisabledApnTypeBitMask = 0;
-        mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-        mCongestedOverride = false;
-        mUnmeteredOverride = false;
-        mDownlinkBandwidth = 14;
-        mUplinkBandwidth = 14;
-        mIsSuspended = false;
-        mHandoverState = HANDOVER_STATE_IDLE;
-        mHandoverFailureMode = DataCallResponse.HANDOVER_FAILURE_MODE_UNKNOWN;
-        mSliceInfo = null;
-        mDefaultQos = null;
-        mDoAllocatePduSessionId = false;
-        mQosBearerSessions.clear();
-        mTrafficDescriptors.clear();
-    }
-
-    /**
-     * Process setup data completion result from data service
-     *
-     * @param resultCode The result code returned by data service
-     * @param response Data call setup response from data service
-     * @param cp The original connection params used for data call setup
-     * @return Setup result
-     */
-    private SetupResult onSetupConnectionCompleted(@DataServiceCallback.ResultCode int resultCode,
-                                                   DataCallResponse response,
-                                                   ConnectionParams cp) {
-        SetupResult result;
-
-        log("onSetupConnectionCompleted: resultCode=" + resultCode + ", response=" + response);
-        if (cp.mTag != mTag) {
-            if (DBG) {
-                log("onSetupConnectionCompleted stale cp.tag=" + cp.mTag + ", mtag=" + mTag);
-            }
-            result = SetupResult.ERROR_STALE;
-        } else if (resultCode == DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE) {
-            result = SetupResult.ERROR_RADIO_NOT_AVAILABLE;
-            result.mFailCause = DataFailCause.RADIO_NOT_AVAILABLE;
-        } else if (resultCode == DataServiceCallback.RESULT_ERROR_TEMPORARILY_UNAVAILABLE) {
-            result = SetupResult.ERROR_DATA_SERVICE_SPECIFIC_ERROR;
-            result.mFailCause = DataFailCause.SERVICE_TEMPORARILY_UNAVAILABLE;
-        } else if (resultCode == DataServiceCallback.RESULT_ERROR_INVALID_ARG) {
-            result = SetupResult.ERROR_INVALID_ARG;
-            result.mFailCause = DataFailCause.UNACCEPTABLE_NETWORK_PARAMETER;
-        } else if (response.getCause() != 0) {
-            if (response.getCause() == DataFailCause.RADIO_NOT_AVAILABLE) {
-                result = SetupResult.ERROR_RADIO_NOT_AVAILABLE;
-                result.mFailCause = DataFailCause.RADIO_NOT_AVAILABLE;
-            } else {
-                result = SetupResult.ERROR_DATA_SERVICE_SPECIFIC_ERROR;
-                result.mFailCause = DataFailCause.getFailCause(response.getCause());
-            }
-        } else if (cp.mApnContext.getApnTypeBitmask() == ApnSetting.TYPE_ENTERPRISE
-                && mDcController.getActiveDcByCid(response.getId()) != null) {
-            if (!mDcController.getTrafficDescriptorsForCid(response.getId())
-                    .equals(response.getTrafficDescriptors())) {
-                if (DBG) log("Updating traffic descriptors: " + response.getTrafficDescriptors());
-                mDcController.getActiveDcByCid(response.getId()).updateTrafficDescriptors(response);
-                mDct.obtainMessage(DctConstants.EVENT_TRAFFIC_DESCRIPTORS_UPDATED).sendToTarget();
-            }
-            if (DBG) log("DataConnection already exists for cid: " + response.getId());
-            result = SetupResult.ERROR_DUPLICATE_CID;
-            result.mFailCause = DataFailCause.DUPLICATE_CID;
-        } else if (cp.mApnContext.getApnTypeBitmask() == ApnSetting.TYPE_ENTERPRISE
-                && !mDcController.isDefaultDataActive()) {
-            if (DBG) log("No default data connection currently active");
-            mCid = response.getId();
-            result = SetupResult.ERROR_NO_DEFAULT_CONNECTION;
-            result.mFailCause = DataFailCause.NO_DEFAULT_DATA;
-        } else {
-            if (DBG) log("onSetupConnectionCompleted received successful DataCallResponse");
-            mCid = response.getId();
-            setPduSessionId(response.getPduSessionId());
-            updatePcscfAddr(response);
-            updateResponseFields(response);
-            result = updateLinkProperty(response).setupResult;
-        }
-
-        return result;
-    }
-
-    private static boolean isResultCodeSuccess(int resultCode) {
-        return resultCode == DataServiceCallback.RESULT_SUCCESS
-                || resultCode == DataServiceCallback.RESULT_ERROR_UNSUPPORTED;
-    }
-
-    private boolean isDnsOk(String[] domainNameServers) {
-        if (NULL_IP.equals(domainNameServers[0]) && NULL_IP.equals(domainNameServers[1])
-                && !mPhone.isDnsCheckDisabled()) {
-            // Work around a race condition where QMI does not fill in DNS:
-            // Deactivate PDP and let DataConnectionTracker retry.
-            // Do not apply the race condition workaround for MMS APN
-            // if Proxy is an IP-address.
-            // Otherwise, the default APN will not be restored anymore.
-            if (!isIpAddress(mApnSetting.getMmsProxyAddressAsString())) {
-                log(String.format(
-                        "isDnsOk: return false apn.types=%d APN_TYPE_MMS=%s isIpAddress(%s)=%s",
-                        mApnSetting.getApnTypeBitmask(), ApnSetting.TYPE_MMS_STRING,
-                        mApnSetting.getMmsProxyAddressAsString(),
-                        isIpAddress(mApnSetting.getMmsProxyAddressAsString())));
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * TCP buffer size config based on the ril technology. There are 6 parameters
-     * read_min, read_default, read_max, write_min, write_default, write_max in the TCP buffer
-     * config string and they are separated by a comma. The unit of these parameters is byte.
-     */
-    private static final String TCP_BUFFER_SIZES_GPRS = "4092,8760,48000,4096,8760,48000";
-    private static final String TCP_BUFFER_SIZES_EDGE = "4093,26280,70800,4096,16384,70800";
-    private static final String TCP_BUFFER_SIZES_UMTS = "58254,349525,1048576,58254,349525,1048576";
-    private static final String TCP_BUFFER_SIZES_1XRTT = "16384,32768,131072,4096,16384,102400";
-    private static final String TCP_BUFFER_SIZES_EVDO = "4094,87380,262144,4096,16384,262144";
-    private static final String TCP_BUFFER_SIZES_EHRPD = "131072,262144,1048576,4096,16384,524288";
-    private static final String TCP_BUFFER_SIZES_HSDPA = "61167,367002,1101005,8738,52429,262114";
-    private static final String TCP_BUFFER_SIZES_HSPA = "40778,244668,734003,16777,100663,301990";
-    private static final String TCP_BUFFER_SIZES_LTE =
-            "524288,1048576,2097152,262144,524288,1048576";
-    private static final String TCP_BUFFER_SIZES_HSPAP =
-            "122334,734003,2202010,32040,192239,576717";
-    private static final String TCP_BUFFER_SIZES_NR =
-            "2097152,6291456,16777216,512000,2097152,8388608";
-    private static final String TCP_BUFFER_SIZES_LTE_CA =
-            "4096,6291456,12582912,4096,1048576,2097152";
-
-    private void updateTcpBufferSizes(int rilRat) {
-        String sizes = null;
-        ServiceState ss = mPhone.getServiceState();
-        if (rilRat == ServiceState.RIL_RADIO_TECHNOLOGY_LTE &&
-                ss.isUsingCarrierAggregation()) {
-            rilRat = ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA;
-        }
-        String ratName = ServiceState.rilRadioTechnologyToString(rilRat).toLowerCase(Locale.ROOT);
-        // ServiceState gives slightly different names for EVDO tech ("evdo-rev.0" for ex)
-        // - patch it up:
-        if (rilRat == ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0 ||
-                rilRat == ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A ||
-                rilRat == ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_B) {
-            ratName = RAT_NAME_EVDO;
-        }
-
-        // NR 5G Non-Standalone use LTE cell as the primary cell, the ril technology is LTE in this
-        // case. We use NR 5G TCP buffer size when connected to NR 5G Non-Standalone network.
-        if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN
-                && ((rilRat == ServiceState.RIL_RADIO_TECHNOLOGY_LTE ||
-                rilRat == ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA) && isNRConnected())
-                && mPhone.getServiceStateTracker().getNrContextIds().contains(mCid)) {
-            ratName = RAT_NAME_5G;
-        }
-
-        log("updateTcpBufferSizes: " + ratName);
-
-        // in the form: "ratname:rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max"
-        String[] configOverride = mPhone.getContext().getResources().getStringArray(
-                com.android.internal.R.array.config_mobile_tcp_buffers);
-        for (int i = 0; i < configOverride.length; i++) {
-            String[] split = configOverride[i].split(":");
-            if (ratName.equals(split[0]) && split.length == 2) {
-                sizes = split[1];
-                break;
-            }
-        }
-
-        if (sizes == null) {
-            // no override - use telephony defaults
-            // doing it this way allows device or carrier to just override the types they
-            // care about and inherit the defaults for the others.
-            switch (rilRat) {
-                case ServiceState.RIL_RADIO_TECHNOLOGY_GPRS:
-                    sizes = TCP_BUFFER_SIZES_GPRS;
-                    break;
-                case ServiceState.RIL_RADIO_TECHNOLOGY_EDGE:
-                    sizes = TCP_BUFFER_SIZES_EDGE;
-                    break;
-                case ServiceState.RIL_RADIO_TECHNOLOGY_UMTS:
-                    sizes = TCP_BUFFER_SIZES_UMTS;
-                    break;
-                case ServiceState.RIL_RADIO_TECHNOLOGY_1xRTT:
-                    sizes = TCP_BUFFER_SIZES_1XRTT;
-                    break;
-                case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0:
-                case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A:
-                case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_B:
-                    sizes = TCP_BUFFER_SIZES_EVDO;
-                    break;
-                case ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD:
-                    sizes = TCP_BUFFER_SIZES_EHRPD;
-                    break;
-                case ServiceState.RIL_RADIO_TECHNOLOGY_HSDPA:
-                    sizes = TCP_BUFFER_SIZES_HSDPA;
-                    break;
-                case ServiceState.RIL_RADIO_TECHNOLOGY_HSPA:
-                case ServiceState.RIL_RADIO_TECHNOLOGY_HSUPA:
-                    sizes = TCP_BUFFER_SIZES_HSPA;
-                    break;
-                case ServiceState.RIL_RADIO_TECHNOLOGY_LTE:
-                    // Use NR 5G TCP buffer size when connected to NR 5G Non-Standalone network.
-                    if (RAT_NAME_5G.equals(ratName)) {
-                        sizes = TCP_BUFFER_SIZES_NR;
-                    } else {
-                        sizes = TCP_BUFFER_SIZES_LTE;
-                    }
-                    break;
-                case ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA:
-                    // Use NR 5G TCP buffer size when connected to NR 5G Non-Standalone network.
-                    if (RAT_NAME_5G.equals(ratName)) {
-                        sizes = TCP_BUFFER_SIZES_NR;
-                    } else {
-                        sizes = TCP_BUFFER_SIZES_LTE_CA;
-                    }
-                    break;
-                case ServiceState.RIL_RADIO_TECHNOLOGY_HSPAP:
-                    sizes = TCP_BUFFER_SIZES_HSPAP;
-                    break;
-                case ServiceState.RIL_RADIO_TECHNOLOGY_NR:
-                    sizes = TCP_BUFFER_SIZES_NR;
-                    break;
-                default:
-                    // Leave empty - this will let ConnectivityService use the system default.
-                    break;
-            }
-        }
-        mLinkProperties.setTcpBufferSizes(sizes);
-    }
-
-    private void updateLinkBandwidthsFromCarrierConfig(int rilRat) {
-        String ratName = DataConfigManager.getDataConfigNetworkType(
-                mPhone.getDisplayInfoController().getTelephonyDisplayInfo());
-
-        if (DBG) log("updateLinkBandwidthsFromCarrierConfig: " + ratName);
-
-        Pair<Integer, Integer> values = mDct.getLinkBandwidthsFromCarrierConfig(ratName);
-        if (values == null) {
-            values = new Pair<>(14, 14);
-        }
-        mDownlinkBandwidth = values.first;
-        mUplinkBandwidth = values.second;
-    }
-
-
-    private void updateLinkBandwidthsFromModem(List<LinkCapacityEstimate> lceList) {
-        if (DBG) log("updateLinkBandwidthsFromModem: lceList=" + lceList);
-        boolean downlinkUpdated = false;
-        boolean uplinkUpdated = false;
-        LinkCapacityEstimate lce = lceList.get(0);
-        // LCE status deprecated in IRadio 1.2, so only check for IRadio < 1.2
-        if (mPhone.getHalVersion().greaterOrEqual(RIL.RADIO_HAL_VERSION_1_2)
-                || mPhone.getLceStatus() == RILConstants.LCE_ACTIVE) {
-            if (lce.getDownlinkCapacityKbps() != LinkCapacityEstimate.INVALID) {
-                mDownlinkBandwidth = lce.getDownlinkCapacityKbps();
-                downlinkUpdated = true;
-            }
-            if (lce.getUplinkCapacityKbps() != LinkCapacityEstimate.INVALID) {
-                mUplinkBandwidth = lce.getUplinkCapacityKbps();
-                uplinkUpdated = true;
-            }
-        }
-
-        if (!downlinkUpdated || !uplinkUpdated) {
-            fallBackToCarrierConfigValues(downlinkUpdated, uplinkUpdated);
-        }
-
-        if (mNetworkAgent != null) {
-            mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities(), DataConnection.this);
-        }
-    }
-
-    private void updateLinkBandwidthsFromBandwidthEstimator(int uplinkBandwidthKbps,
-            int downlinkBandwidthKbps) {
-        if (DBG) {
-            log("updateLinkBandwidthsFromBandwidthEstimator, UL= "
-                    + uplinkBandwidthKbps + " DL= " + downlinkBandwidthKbps);
-        }
-        boolean downlinkUpdated = false;
-        boolean uplinkUpdated = false;
-        if (downlinkBandwidthKbps > 0) {
-            mDownlinkBandwidth = downlinkBandwidthKbps;
-            downlinkUpdated = true;
-        }
-        if (uplinkBandwidthKbps > 0) {
-            mUplinkBandwidth = uplinkBandwidthKbps;
-            uplinkUpdated = true;
-        }
-
-        if (!downlinkUpdated || !uplinkUpdated) {
-            fallBackToCarrierConfigValues(downlinkUpdated, uplinkUpdated);
-        }
-        if (mNetworkAgent != null) {
-            mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities(), DataConnection.this);
-        }
-    }
-
-    private void fallBackToCarrierConfigValues(boolean downlinkUpdated, boolean uplinkUpdated) {
-        String ratName = ServiceState.rilRadioTechnologyToString(mRilRat);
-        if (mRilRat == ServiceState.RIL_RADIO_TECHNOLOGY_LTE && isNRConnected()) {
-            ratName = mPhone.getServiceState().getNrFrequencyRange()
-                    == ServiceState.FREQUENCY_RANGE_MMWAVE
-                    ? DctConstants.RAT_NAME_NR_NSA_MMWAVE : DctConstants.RAT_NAME_NR_NSA;
-        }
-        Pair<Integer, Integer> values = mDct.getLinkBandwidthsFromCarrierConfig(ratName);
-        if (values != null) {
-            if (!downlinkUpdated) {
-                mDownlinkBandwidth = values.first;
-            }
-            if (!uplinkUpdated) {
-                mUplinkBandwidth = values.second;
-            }
-            mUplinkBandwidth = Math.min(mUplinkBandwidth, mDownlinkBandwidth);
-        }
-    }
-
-    private boolean isBandwidthSourceKey(String source) {
-        return source.equals(mPhone.getContext().getResources().getString(
-                com.android.internal.R.string.config_bandwidthEstimateSource));
-    }
-
-    /**
-     * Indicates if this data connection was established for unmetered use only. Note that this
-     * flag should be populated when data becomes active. And if it is set to true, it can be set to
-     * false later when we are reevaluating the data connection. But if it is set to false, it
-     * can never become true later because setting it to true will cause this data connection
-     * losing some immutable network capabilities, which can cause issues in connectivity service.
-     */
-    private boolean mUnmeteredUseOnly = false;
-
-    /**
-     * Indicates if this data connection was established for MMS use only. This is true only when
-     * mobile data is disabled but the user allows sending and receiving MMS messages. If the data
-     * enabled settings indicate that MMS data is allowed unconditionally, MMS can be sent when data
-     * is disabled even if it is a metered APN type.
-     */
-    private boolean mMmsUseOnly = false;
-
-    /**
-     * Indicates if when this connection was established we had a restricted/privileged
-     * NetworkRequest and needed it to overcome data-enabled limitations.
-     *
-     * This flag overrides the APN-based restriction capability, restricting the network
-     * based on both having a NetworkRequest with restricted AND needing a restricted
-     * bit to overcome user-disabled status.  This allows us to handle the common case
-     * of having both restricted requests and unrestricted requests for the same apn:
-     * if conditions require a restricted network to overcome user-disabled then it must
-     * be restricted, otherwise it is unrestricted (or restricted based on APN type).
-     *
-     * This supports a privileged app bringing up a network without general apps having access
-     * to it when the network is otherwise unavailable (hipri).  The first use case is
-     * pre-paid SIM reprovisioning over internet, where the carrier insists on no traffic
-     * other than from the privileged carrier-app.
-     *
-     * Note that the data connection cannot go from unrestricted to restricted because the
-     * connectivity service does not support dynamically closing TCP connections at this point.
-     */
-    private boolean mRestrictedNetworkOverride = false;
-
-    /**
-     * Indicates if this data connection supports enterprise use. Note that this flag should be
-     * populated when data becomes active. Once it is set, the value cannot be changed because
-     * setting it will cause this data connection to lose immutable network capabilities, which can
-     * cause issues in connectivity service.
-     */
-    private boolean mEnterpriseUse = false;
-
-    /**
-     * Check if this data connection should be restricted. We should call this when data connection
-     * becomes active, or when we want to re-evaluate the conditions to decide if we need to
-     * unstrict the data connection.
-     *
-     * @return True if this data connection needs to be restricted.
-     */
-    private boolean shouldRestrictNetwork() {
-        // first, check if there is any network request that containing restricted capability
-        // (i.e. Do not have NET_CAPABILITY_NOT_RESTRICTED in the request)
-        boolean isAnyRestrictedRequest = false;
-        for (ApnContext apnContext : mApnContexts.keySet()) {
-            if (apnContext.hasRestrictedRequests(true /* exclude DUN */)) {
-                isAnyRestrictedRequest = true;
-                break;
-            }
-        }
-
-        // If all of the network requests are non-restricted, then we don't need to restrict
-        // the network.
-        if (!isAnyRestrictedRequest) {
-            return false;
-        }
-
-        // If the network is unmetered, then we don't need to restrict the network because users
-        // won't be charged anyway.
-        if (!ApnSettingUtils.isMetered(mApnSetting, mPhone)) {
-            return false;
-        }
-
-        // If the data is disabled, then we need to restrict the network so only privileged apps can
-        // use the restricted network while data is disabled.
-        if (!mPhone.getDataEnabledSettings().isDataEnabled()) {
-            return true;
-        }
-
-        // If the device is roaming, and the user does not turn on data roaming, then we need to
-        // restrict the network so only privileged apps can use it.
-        if (!mDct.getDataRoamingEnabled() && mPhone.getServiceState().getDataRoaming()) {
-            return true;
-        }
-
-        // Otherwise we should not restrict the network so anyone who requests can use it.
-        return false;
-    }
-
-    /**
-     * @return True if this data connection should only be used for unmetered purposes.
-     */
-    private boolean isUnmeteredUseOnly() {
-        // If this data connection is on IWLAN, then it's unmetered and can be used by everyone.
-        // Should not be for unmetered used only.
-        if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
-            return false;
-        }
-
-        // If data is enabled, this data connection can't be for unmetered used only because
-        // everyone should be able to use it if:
-        // 1. Device is not roaming, or
-        // 2. Device is roaming and data roaming is turned on
-        if (mPhone.getDataEnabledSettings().isDataEnabled()) {
-            if (!mPhone.getServiceState().getDataRoaming() || mDct.getDataRoamingEnabled()) {
-                return false;
-            }
-        }
-
-        // The data connection can only be unmetered used only if all attached APN contexts
-        // attached to this data connection are unmetered.
-        for (ApnContext apnContext : mApnContexts.keySet()) {
-            if (ApnSettingUtils.isMeteredApnType(apnContext.getApnTypeBitmask(), mPhone)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * @return True if this data connection should only be used for MMS purposes.
-     */
-    private boolean isMmsUseOnly() {
-        // MMS use only if data is disabled, MMS is allowed unconditionally, and MMS is the only
-        // APN type for this data connection.
-        DataEnabledSettings des = mPhone.getDataEnabledSettings();
-        boolean mmsAllowedUnconditionally = !des.isDataEnabled() && des.isMmsAlwaysAllowed();
-        boolean mmsApnOnly = isApnContextAttached(ApnSetting.TYPE_MMS, true);
-        return mmsAllowedUnconditionally && mmsApnOnly;
-    }
-
-    /**
-     * Check if this data connection supports enterprise use. We call this when the data connection
-     * becomes active or when we want to reevaluate the conditions to decide if we need to update
-     * the network agent capabilities.
-     *
-     * @return True if this data connection supports enterprise use.
-     */
-    private boolean isEnterpriseUse() {
-        return  mApnContexts.keySet().stream().anyMatch(
-                ac -> ac.getApnTypeBitmask() == ApnSetting.TYPE_ENTERPRISE);
-    }
-
-    /**
-     * Get the network capabilities for this data connection.
-     *
-     * @return the {@link NetworkCapabilities} of this data connection.
-     */
-    public NetworkCapabilities getNetworkCapabilities() {
-        final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder()
-                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
-        boolean unmeteredApns = false;
-
-        if (mApnSetting != null && !mEnterpriseUse && !mMmsUseOnly) {
-            final int[] types = ApnSetting.getApnTypesFromBitmask(
-                    mApnSetting.getApnTypeBitmask() & ~mDisabledApnTypeBitMask);
-            for (int type : types) {
-                if ((!mRestrictedNetworkOverride && mUnmeteredUseOnly)
-                        && ApnSettingUtils.isMeteredApnType(type, mPhone)) {
-                    log("Dropped the metered " + ApnSetting.getApnTypeString(type)
-                            + " type for the unmetered data call.");
-                    continue;
-                }
-                switch (type) {
-                    case ApnSetting.TYPE_ALL: {
-                        builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
-                        builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
-                        builder.addCapability(NetworkCapabilities.NET_CAPABILITY_SUPL);
-                        builder.addCapability(NetworkCapabilities.NET_CAPABILITY_FOTA);
-                        builder.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
-                        builder.addCapability(NetworkCapabilities.NET_CAPABILITY_CBS);
-                        builder.addCapability(NetworkCapabilities.NET_CAPABILITY_IA);
-                        builder.addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
-                        break;
-                    }
-                    case ApnSetting.TYPE_DEFAULT: {
-                        builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
-                        break;
-                    }
-                    case ApnSetting.TYPE_MMS: {
-                        builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
-                        break;
-                    }
-                    case ApnSetting.TYPE_SUPL: {
-                        builder.addCapability(NetworkCapabilities.NET_CAPABILITY_SUPL);
-                        break;
-                    }
-                    case ApnSetting.TYPE_DUN: {
-                        builder.addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
-                        break;
-                    }
-                    case ApnSetting.TYPE_FOTA: {
-                        builder.addCapability(NetworkCapabilities.NET_CAPABILITY_FOTA);
-                        break;
-                    }
-                    case ApnSetting.TYPE_IMS: {
-                        builder.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
-                        break;
-                    }
-                    case ApnSetting.TYPE_CBS: {
-                        builder.addCapability(NetworkCapabilities.NET_CAPABILITY_CBS);
-                        break;
-                    }
-                    case ApnSetting.TYPE_IA: {
-                        builder.addCapability(NetworkCapabilities.NET_CAPABILITY_IA);
-                        break;
-                    }
-                    case ApnSetting.TYPE_EMERGENCY: {
-                        builder.addCapability(NetworkCapabilities.NET_CAPABILITY_EIMS);
-                        break;
-                    }
-                    case ApnSetting.TYPE_MCX: {
-                        builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MCX);
-                        break;
-                    }
-                    case ApnSetting.TYPE_XCAP: {
-                        builder.addCapability(NetworkCapabilities.NET_CAPABILITY_XCAP);
-                        break;
-                    }
-                    default:
-                }
-            }
-
-            if (!ApnSettingUtils.isMetered(mApnSetting, mPhone)) {
-                unmeteredApns = true;
-            }
-        }
-
-        // Mark NOT_METERED in the following cases:
-        // 1. All APNs in the APN settings are unmetered.
-        // 2. The non-restricted data is intended for unmetered use only.
-        if (unmeteredApns || (mUnmeteredUseOnly && !mRestrictedNetworkOverride)) {
-            builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
-        } else {
-            builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
-        }
-
-        if (mEnterpriseUse) {
-            builder.addCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE);
-            builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
-        }
-
-        if (NetworkCapabilitiesUtils.inferRestrictedCapability(builder.build())) {
-            builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
-        }
-
-        if (mMmsUseOnly) {
-            if (ApnSettingUtils.isMeteredApnType(ApnSetting.TYPE_MMS, mPhone)) {
-                log("Adding unmetered capability for the unmetered MMS-only data connection");
-                builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
-            }
-            log("Adding MMS capability for the MMS-only data connection");
-            builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
-        }
-
-        if (mRestrictedNetworkOverride) {
-            builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
-            // don't use dun on restriction-overriden networks.
-            builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
-        }
-
-        builder.setLinkDownstreamBandwidthKbps(mDownlinkBandwidth);
-        builder.setLinkUpstreamBandwidthKbps(mUplinkBandwidth);
-
-        builder.setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
-                .setSubscriptionId(mSubId).build());
-        builder.setSubscriptionIds(Collections.singleton(mSubId));
-
-        if (!mPhone.getServiceState().getDataRoaming()) {
-            builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
-        }
-
-        if (!mCongestedOverride) {
-            builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED);
-        }
-
-        if (mUnmeteredOverride) {
-            builder.addCapability(NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED);
-        }
-
-        if (!mIsSuspended) {
-            builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
-        }
-
-        final int carrierServicePackageUid = getCarrierServicePackageUid();
-
-        // TODO(b/205736323): Owner and Admin UIDs currently come from separate data sources. Unify
-        //                    them, and remove ArrayUtils.contains() check.
-        if (carrierServicePackageUid != Process.INVALID_UID
-                && ArrayUtils.contains(mAdministratorUids, carrierServicePackageUid)) {
-            builder.setOwnerUid(carrierServicePackageUid);
-            builder.setAllowedUids(Collections.singleton(carrierServicePackageUid));
-        }
-        builder.setAdministratorUids(mAdministratorUids);
-
-        // Always start with NOT_VCN_MANAGED, then remove if VcnManager indicates this is part of a
-        // VCN.
-        builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
-        final VcnNetworkPolicyResult vcnPolicy = getVcnPolicy(builder.build());
-        if (!vcnPolicy.getNetworkCapabilities()
-                .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)) {
-            builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
-        }
-        if (!vcnPolicy.getNetworkCapabilities()
-                .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)) {
-            builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
-        }
-
-        return builder.build();
-    }
-
-    // TODO(b/205736323): Once TelephonyManager#getCarrierServicePackageNameForLogicalSlot() is
-    //                    plumbed to CarrierPrivilegesTracker's cache, query the cached UIDs.
-    private int getFirstUidForPackage(String pkgName) {
-        if (pkgName == null) {
-            return Process.INVALID_UID;
-        }
-
-        List<UserInfo> users = mPhone.getContext().getSystemService(UserManager.class).getUsers();
-        for (UserInfo user : users) {
-            int userId = user.getUserHandle().getIdentifier();
-            try {
-                PackageManager pm = mPhone.getContext().getPackageManager();
-
-                if (pm != null) {
-                    return pm.getPackageUidAsUser(pkgName, userId);
-                }
-            } catch (NameNotFoundException exception) {
-                // Didn't find package. Try other users
-                Rlog.i(
-                        "DataConnection",
-                        "Unable to find uid for package " + pkgName + " and user " + userId);
-            }
-        }
-        return Process.INVALID_UID;
-    }
-
-    private int getCarrierServicePackageUid() {
-        String pkgName =
-                mPhone.getContext()
-                        .getSystemService(TelephonyManager.class)
-                        .getCarrierServicePackageNameForLogicalSlot(mPhone.getPhoneId());
-
-        return getFirstUidForPackage(pkgName);
-    }
-
-    /**
-     * Check if the this data network is VCN-managed.
-     *
-     * @param networkCapabilities The network capabilities of this data network.
-     * @return The VCN's policy for this DataNetwork.
-     */
-    private VcnNetworkPolicyResult getVcnPolicy(NetworkCapabilities networkCapabilities) {
-        return mVcnManager.applyVcnNetworkPolicy(networkCapabilities, getLinkProperties());
-    }
-
-    /** @return {@code true} if validation is required, {@code false} otherwise. */
-    public boolean isValidationRequired() {
-        final NetworkCapabilities nc = getNetworkCapabilities();
-        return nc != null
-                && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
-                && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
-                && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
-    }
-
-    /**
-     * @return {@code True} if 464xlat should be skipped.
-     */
-    @VisibleForTesting
-    public boolean shouldSkip464Xlat() {
-        switch (mApnSetting.getSkip464Xlat()) {
-            case Telephony.Carriers.SKIP_464XLAT_ENABLE:
-                return true;
-            case Telephony.Carriers.SKIP_464XLAT_DISABLE:
-                return false;
-            case Telephony.Carriers.SKIP_464XLAT_DEFAULT:
-            default:
-                break;
-        }
-
-        // As default, return true if ims and no internet
-        final NetworkCapabilities nc = getNetworkCapabilities();
-        return nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
-                && !nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
-    }
-
-    /**
-     * @return {@code} true iff. {@code address} is a literal IPv4 or IPv6 address.
-     */
-    @VisibleForTesting
-    public static boolean isIpAddress(String address) {
-        if (address == null) return false;
-
-        // Accept IPv6 addresses (only) in square brackets for compatibility.
-        if (address.startsWith("[") && address.endsWith("]") && address.indexOf(':') != -1) {
-            address = address.substring(1, address.length() - 1);
-        }
-        return InetAddresses.isNumericAddress(address);
-    }
-
-    private SetupResult setLinkProperties(DataCallResponse response,
-            LinkProperties linkProperties) {
-        // Check if system property dns usable
-        String propertyPrefix = "net." + response.getInterfaceName() + ".";
-        String dnsServers[] = new String[2];
-        dnsServers[0] = SystemProperties.get(propertyPrefix + "dns1");
-        dnsServers[1] = SystemProperties.get(propertyPrefix + "dns2");
-        boolean okToUseSystemPropertyDns = isDnsOk(dnsServers);
-
-        SetupResult result;
-
-        // Start with clean network properties and if we have
-        // a failure we'll clear again at the bottom of this code.
-        linkProperties.clear();
-
-        if (response.getCause() == DataFailCause.NONE) {
-            try {
-                // set interface name
-                linkProperties.setInterfaceName(response.getInterfaceName());
-
-                // set link addresses
-                if (response.getAddresses().size() > 0) {
-                    for (LinkAddress la : response.getAddresses()) {
-                        if (!la.getAddress().isAnyLocalAddress()) {
-                            if (DBG) {
-                                log("addr/pl=" + la.getAddress() + "/"
-                                        + la.getPrefixLength());
-                            }
-                            linkProperties.addLinkAddress(la);
-                        }
-                    }
-                } else {
-                    throw new UnknownHostException("no address for ifname="
-                            + response.getInterfaceName());
-                }
-
-                // set dns servers
-                if (response.getDnsAddresses().size() > 0) {
-                    for (InetAddress dns : response.getDnsAddresses()) {
-                        if (!dns.isAnyLocalAddress()) {
-                            linkProperties.addDnsServer(dns);
-                        }
-                    }
-                } else if (okToUseSystemPropertyDns) {
-                    for (String dnsAddr : dnsServers) {
-                        dnsAddr = dnsAddr.trim();
-                        if (dnsAddr.isEmpty()) continue;
-                        InetAddress ia;
-                        try {
-                            ia = InetAddresses.parseNumericAddress(dnsAddr);
-                        } catch (IllegalArgumentException e) {
-                            throw new UnknownHostException("Non-numeric dns addr=" + dnsAddr);
-                        }
-                        if (!ia.isAnyLocalAddress()) {
-                            linkProperties.addDnsServer(ia);
-                        }
-                    }
-                } else {
-                    throw new UnknownHostException("Empty dns response and no system default dns");
-                }
-
-                // set pcscf
-                if (response.getPcscfAddresses().size() > 0) {
-                    for (InetAddress pcscf : response.getPcscfAddresses()) {
-                        linkProperties.addPcscfServer(pcscf);
-                    }
-                }
-
-                for (InetAddress gateway : response.getGatewayAddresses()) {
-                    int mtu = gateway instanceof java.net.Inet6Address ? response.getMtuV6()
-                            : response.getMtuV4();
-                    // Allow 0.0.0.0 or :: as a gateway;
-                    // this indicates a point-to-point interface.
-                    linkProperties.addRoute(new RouteInfo(null, gateway, null,
-                            RouteInfo.RTN_UNICAST, mtu));
-                }
-
-                // set interface MTU
-                // this may clobber the setting read from the APN db, but that's ok
-                // TODO: remove once LinkProperties#setMtu is deprecated
-                linkProperties.setMtu(response.getMtu());
-
-                result = SetupResult.SUCCESS;
-            } catch (UnknownHostException e) {
-                log("setLinkProperties: UnknownHostException " + e);
-                result = SetupResult.ERROR_INVALID_ARG;
-            }
-        } else {
-            result = SetupResult.ERROR_DATA_SERVICE_SPECIFIC_ERROR;
-        }
-
-        // An error occurred so clear properties
-        if (result != SetupResult.SUCCESS) {
-            if (DBG) {
-                log("setLinkProperties: error clearing LinkProperties status="
-                        + response.getCause() + " result=" + result);
-            }
-            linkProperties.clear();
-        }
-
-        return result;
-    }
-
-    /**
-     * Initialize connection, this will fail if the
-     * apnSettings are not compatible.
-     *
-     * @param cp the Connection parameters
-     * @return true if initialization was successful.
-     */
-    private boolean initConnection(ConnectionParams cp) {
-        ApnContext apnContext = cp.mApnContext;
-        if (mApnSetting == null) {
-            // Only change apn setting if it isn't set, it will
-            // only NOT be set only if we're in DcInactiveState.
-            mApnSetting = apnContext.getApnSetting();
-        }
-        if (mApnSetting == null || (!mApnSetting.canHandleType(apnContext.getApnTypeBitmask())
-                && apnContext.getApnTypeBitmask() != ApnSetting.TYPE_ENTERPRISE)) {
-            if (DBG) {
-                log("initConnection: incompatible apnSetting in ConnectionParams cp=" + cp
-                        + " dc=" + DataConnection.this);
-            }
-            return false;
-        }
-        mTag += 1;
-        mConnectionParams = cp;
-        mConnectionParams.mTag = mTag;
-
-        // always update the ConnectionParams with the latest or the
-        // connectionGeneration gets stale
-        mApnContexts.put(apnContext, cp);
-
-        if (DBG) {
-            log("initConnection: "
-                    + " RefCount=" + mApnContexts.size()
-                    + " mApnList=" + mApnContexts
-                    + " mConnectionParams=" + mConnectionParams);
-        }
-        return true;
-    }
-
-    /**
-     * The parent state for all other states.
-     */
-    private class DcDefaultState extends State {
-        @Override
-        public void enter() {
-            if (DBG) log("DcDefaultState: enter");
-
-            // Register for DRS or RAT change
-            mPhone.getServiceStateTracker().registerForDataRegStateOrRatChanged(
-                    mTransportType, getHandler(),
-                    DataConnection.EVENT_DATA_CONNECTION_DRS_OR_RAT_CHANGED, null);
-
-            mPhone.getServiceStateTracker().registerForDataRoamingOn(getHandler(),
-                    DataConnection.EVENT_DATA_CONNECTION_ROAM_ON, null);
-            mPhone.getServiceStateTracker().registerForDataRoamingOff(getHandler(),
-                    DataConnection.EVENT_DATA_CONNECTION_ROAM_OFF, null, true);
-            mPhone.getServiceStateTracker().registerForNrStateChanged(getHandler(),
-                    DataConnection.EVENT_NR_STATE_CHANGED, null);
-            mPhone.getServiceStateTracker().registerForNrFrequencyChanged(getHandler(),
-                    DataConnection.EVENT_NR_FREQUENCY_CHANGED, null);
-            mPhone.getServiceStateTracker().registerForCssIndicatorChanged(getHandler(),
-                    DataConnection.EVENT_CSS_INDICATOR_CHANGED, null);
-            if (isBandwidthSourceKey(DctConstants.BANDWIDTH_SOURCE_BANDWIDTH_ESTIMATOR_KEY)) {
-                mPhone.getLinkBandwidthEstimator().registerForBandwidthChanged(getHandler(),
-                        DataConnection.EVENT_LINK_BANDWIDTH_ESTIMATOR_UPDATE, null);
-            }
-
-            // Add ourselves to the list of data connections
-            mDcController.addDc(DataConnection.this);
-        }
-        @Override
-        public void exit() {
-            if (DBG) log("DcDefaultState: exit");
-
-            // Unregister for DRS or RAT change.
-            mPhone.getServiceStateTracker().unregisterForDataRegStateOrRatChanged(
-                    mTransportType, getHandler());
-
-            mPhone.getServiceStateTracker().unregisterForDataRoamingOn(getHandler());
-            mPhone.getServiceStateTracker().unregisterForDataRoamingOff(getHandler());
-            mPhone.getServiceStateTracker().unregisterForNrStateChanged(getHandler());
-            mPhone.getServiceStateTracker().unregisterForNrFrequencyChanged(getHandler());
-            mPhone.getServiceStateTracker().unregisterForCssIndicatorChanged(getHandler());
-            if (isBandwidthSourceKey(DctConstants.BANDWIDTH_SOURCE_BANDWIDTH_ESTIMATOR_KEY)) {
-                mPhone.getLinkBandwidthEstimator().unregisterForBandwidthChanged(getHandler());
-            }
-
-            // Remove ourselves from the DC lists
-            mDcController.removeDc(DataConnection.this);
-
-            if (mAc != null) {
-                mAc.disconnected();
-                mAc = null;
-            }
-            mApnContexts.clear();
-            mReconnectIntent = null;
-            mDct = null;
-            mApnSetting = null;
-            mPhone = null;
-            mDataServiceManager = null;
-            mLinkProperties = null;
-            mLastFailCause = DataFailCause.NONE;
-            mUserData = null;
-            mDcController = null;
-            mDcTesterFailBringUpAll = null;
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            boolean retVal = HANDLED;
-
-            if (VDBG) {
-                log("DcDefault msg=" + getWhatToString(msg.what)
-                        + " RefCount=" + mApnContexts.size());
-            }
-            switch (msg.what) {
-                case EVENT_RESET:
-                    if (VDBG) log("DcDefaultState: msg.what=REQ_RESET");
-                    transitionTo(mInactiveState);
-                    break;
-                case EVENT_CONNECT:
-                    if (DBG) log("DcDefaultState: msg.what=EVENT_CONNECT, fail not expected");
-                    ConnectionParams cp = (ConnectionParams) msg.obj;
-                    notifyConnectCompleted(cp, DataFailCause.UNKNOWN,
-                            DataCallResponse.HANDOVER_FAILURE_MODE_UNKNOWN, false);
-                    break;
-
-                case EVENT_DISCONNECT:
-                case EVENT_DISCONNECT_ALL:
-                case EVENT_REEVALUATE_RESTRICTED_STATE:
-                    if (DBG) {
-                        log("DcDefaultState deferring msg.what=" + getWhatToString(msg.what)
-                                + " RefCount=" + mApnContexts.size());
-                    }
-                    deferMessage(msg);
-                    break;
-                case EVENT_TEAR_DOWN_NOW:
-                    if (DBG) log("DcDefaultState EVENT_TEAR_DOWN_NOW");
-                    mDataServiceManager.deactivateDataCall(mCid, DataService.REQUEST_REASON_NORMAL,
-                            null);
-                    mDataCallSessionStats.setDeactivateDataCallReason(
-                            DataService.REQUEST_REASON_NORMAL);
-                    break;
-                case EVENT_LOST_CONNECTION:
-                    if (DBG) {
-                        String s = "DcDefaultState ignore EVENT_LOST_CONNECTION"
-                                + " tag=" + msg.arg1 + ":mTag=" + mTag;
-                        logAndAddLogRec(s);
-                    }
-                    break;
-                case EVENT_DATA_CONNECTION_DRS_OR_RAT_CHANGED:
-                    AsyncResult ar = (AsyncResult)msg.obj;
-                    Pair<Integer, Integer> drsRatPair = (Pair<Integer, Integer>)ar.result;
-                    mDataRegState = drsRatPair.first;
-                    updateTcpBufferSizes(drsRatPair.second);
-                    if (isBandwidthSourceKey(DctConstants.BANDWIDTH_SOURCE_CARRIER_CONFIG_KEY)) {
-                        updateLinkBandwidthsFromCarrierConfig(drsRatPair.second);
-                    }
-                    mRilRat = drsRatPair.second;
-                    if (DBG) {
-                        log("DcDefaultState: EVENT_DATA_CONNECTION_DRS_OR_RAT_CHANGED"
-                                + " regState=" + ServiceState.rilServiceStateToString(mDataRegState)
-                                + " RAT=" + ServiceState.rilRadioTechnologyToString(mRilRat));
-                    }
-                    mDataCallSessionStats.onDrsOrRatChanged(
-                            ServiceState.rilRadioTechnologyToNetworkType(mRilRat));
-                    break;
-
-                case EVENT_START_HANDOVER:  //calls startHandover()
-                    if (DBG) {
-                        log("DcDefaultState: EVENT_START_HANDOVER not expected.");
-                    }
-                    Consumer<Integer> r = (Consumer<Integer>) msg.obj;
-                    r.accept(DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
-                    break;
-                case EVENT_START_HANDOVER_ON_TARGET:
-                    if (DBG) {
-                        log("DcDefaultState: EVENT_START_HANDOVER not expected, but will "
-                                + "clean up, result code: "
-                                + DataServiceCallback.resultCodeToString(msg.arg1));
-                    }
-                    ((Consumer<Boolean>) msg.obj).accept(false /* is in correct state*/);
-                    break;
-                case EVENT_CANCEL_HANDOVER:
-                    // We don't need to do anything in this case
-                    if (DBG) {
-                        log("DcDefaultState: EVENT_CANCEL_HANDOVER resultCode="
-                                + DataServiceCallback.resultCodeToString(msg.arg1));
-                    }
-                    break;
-                case EVENT_RELEASE_PDU_SESSION_ID: {
-                    // We do the same thing in all state in order to preserve the existing workflow
-                    final AsyncResult asyncResult = (AsyncResult) msg.obj;
-                    if (asyncResult == null) {
-                        loge("EVENT_RELEASE_PDU_SESSION_ID: asyncResult is null!");
-                    } else {
-                        if (msg.obj != null) {
-                            if (DBG) logd("EVENT_RELEASE_PDU_SESSION_ID: id released");
-                            Runnable runnable = (Runnable) asyncResult.userObj;
-                            runnable.run();
-                        } else {
-                            loge("EVENT_RELEASE_PDU_SESSION_ID: no runnable set");
-                        }
-                    }
-                    retVal = HANDLED;
-                    break;
-                }
-                case EVENT_ALLOCATE_PDU_SESSION_ID: {
-                    // We do the same thing in all state in order to preserve the existing workflow
-                    final AsyncResult asyncResult = (AsyncResult) msg.obj;
-                    if (asyncResult == null) {
-                        loge("EVENT_ALLOCATE_PDU_SESSION_ID: asyncResult is null!");
-                    } else {
-                        Consumer<Integer> onAllocated = (Consumer<Integer>) asyncResult.userObj;
-                        if (asyncResult.exception != null) {
-                            loge("EVENT_ALLOCATE_PDU_SESSION_ID: exception",
-                                    asyncResult.exception);
-                            onAllocated.accept(PDU_SESSION_ID_NOT_SET);
-                        } else if (asyncResult.result == null) {
-                            loge("EVENT_ALLOCATE_PDU_SESSION_ID: result null, no id");
-                            onAllocated.accept(PDU_SESSION_ID_NOT_SET);
-                        } else {
-                            int psi = (int) asyncResult.result;
-                            if (DBG) logd("EVENT_ALLOCATE_PDU_SESSION_ID: psi=" + psi);
-                            onAllocated.accept(psi);
-                        }
-                    }
-                    retVal = HANDLED;
-                    break;
-                }
-                default:
-                    if (DBG) {
-                        log("DcDefaultState: ignore msg.what=" + getWhatToString(msg.what));
-                    }
-                    break;
-            }
-
-            return retVal;
-        }
-    }
-
-    private void updateSuspendState() {
-        if (mNetworkAgent == null) {
-            Rlog.d(getName(), "Setting suspend state without a NetworkAgent");
-        }
-
-        boolean newSuspendedState = false;
-        // Data can only be (temporarily) suspended while data is in active state
-        if (getCurrentState() == mActiveState) {
-            // Never set suspended for emergency apn. Emergency data connection
-            // can work while device is not in service.
-            if (mApnSetting != null && mApnSetting.isEmergencyApn()) {
-                newSuspendedState = false;
-            // If we are not in service, change to suspended.
-            } else if (mDataRegState != ServiceState.STATE_IN_SERVICE) {
-                newSuspendedState = true;
-            // Check voice/data concurrency.
-            } else if (!mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) {
-                newSuspendedState = mPhone.getCallTracker().getState() != PhoneConstants.State.IDLE;
-            }
-        }
-
-        // Only notify when there is a change.
-        if (mIsSuspended != newSuspendedState) {
-            mIsSuspended = newSuspendedState;
-
-            // If data connection is active, we need to notify the new data connection state
-            // changed event reflecting the latest suspended state.
-            if (isActive()) {
-                notifyDataConnectionState();
-            }
-        }
-    }
-
-    private void notifyDataConnectionState() {
-        // The receivers of this have no way to differentiate between default and enterprise
-        // connections. Do not notify for enterprise.
-        if (!isEnterpriseUse()) {
-            mPhone.notifyDataConnection(getPreciseDataConnectionState());
-        } else {
-            log("notifyDataConnectionState: Skipping for enterprise; state=" + getState());
-        }
-    }
-
-    private DcDefaultState mDefaultState = new DcDefaultState();
-
-    private int getApnTypeBitmask() {
-        return isEnterpriseUse() ? ApnSetting.TYPE_ENTERPRISE :
-                mApnSetting != null ? mApnSetting.getApnTypeBitmask() : 0;
-    }
-
-    private boolean canHandleDefault() {
-        return !isEnterpriseUse() && mApnSetting != null
-                ? mApnSetting.canHandleType(ApnSetting.TYPE_DEFAULT) : false;
-    }
-
-    /**
-     * The state machine is inactive and expects a EVENT_CONNECT.
-     */
-    private class DcInactiveState extends State {
-        // Inform all contexts we've failed connecting
-        public void setEnterNotificationParams(ConnectionParams cp, @DataFailureCause int cause,
-                @HandoverFailureMode int handoverFailureMode) {
-            if (VDBG) log("DcInactiveState: setEnterNotificationParams cp,cause");
-            mConnectionParams = cp;
-            mDisconnectParams = null;
-            mDcFailCause = cause;
-            mHandoverFailureMode = handoverFailureMode;
-        }
-
-        // Inform all contexts we've failed disconnected
-        public void setEnterNotificationParams(DisconnectParams dp) {
-            if (VDBG) log("DcInactiveState: setEnterNotificationParams dp");
-            mConnectionParams = null;
-            mDisconnectParams = dp;
-            mDcFailCause = DataFailCause.NONE;
-        }
-
-        // Inform all contexts of the failure cause
-        public void setEnterNotificationParams(@DataFailureCause int cause) {
-            mConnectionParams = null;
-            mDisconnectParams = null;
-            mDcFailCause = cause;
-        }
-
-        @Override
-        public void enter() {
-            mTag += 1;
-            if (DBG) log("DcInactiveState: enter() mTag=" + mTag);
-            TelephonyStatsLog.write(TelephonyStatsLog.MOBILE_CONNECTION_STATE_CHANGED,
-                    TelephonyStatsLog.MOBILE_CONNECTION_STATE_CHANGED__STATE__INACTIVE,
-                    mPhone.getPhoneId(), mId, getApnTypeBitmask(), canHandleDefault());
-            mDataCallSessionStats.onDataCallDisconnected(mDcFailCause);
-            if (mHandoverState == HANDOVER_STATE_BEING_TRANSFERRED) {
-                // This is from source data connection to set itself's state
-                setHandoverState(HANDOVER_STATE_COMPLETED);
-            }
-
-            // Check for dangling agent. Ideally the handover source agent should be null if
-            // handover process is smooth. When it's not null, that means handover failed. The
-            // agent was not successfully transferred to the new data connection. We should
-            // gracefully notify connectivity service the network was disconnected.
-            if (mHandoverSourceNetworkAgent != null) {
-                DataConnection sourceDc = mHandoverSourceNetworkAgent.getDataConnection();
-                if (sourceDc != null) {
-                    // If the source data connection still owns this agent, then just reset the
-                    // handover state back to idle because handover is already failed.
-                    mHandoverLocalLog.log(
-                            "Handover failed. Reset the source dc " + sourceDc.getName()
-                                    + " state to idle");
-                    sourceDc.cancelHandover();
-                } else {
-                    // The agent is now a dangling agent. No data connection owns this agent.
-                    // Gracefully notify connectivity service disconnected.
-                    mHandoverLocalLog.log(
-                            "Handover failed and dangling agent found.");
-                    mHandoverSourceNetworkAgent.acquireOwnership(
-                            DataConnection.this, mTransportType);
-                    log("Cleared dangling network agent. " + mHandoverSourceNetworkAgent);
-                    mHandoverSourceNetworkAgent.unregister(DataConnection.this);
-                    mHandoverSourceNetworkAgent.releaseOwnership(DataConnection.this);
-                }
-                mHandoverSourceNetworkAgent = null;
-            }
-
-            if (mConnectionParams != null) {
-                if (DBG) {
-                    log("DcInactiveState: enter notifyConnectCompleted +ALL failCause="
-                            + DataFailCause.toString(mDcFailCause));
-                }
-                notifyConnectCompleted(mConnectionParams, mDcFailCause, mHandoverFailureMode,
-                        true);
-            }
-            if (mDisconnectParams != null) {
-                if (DBG) {
-                    log("DcInactiveState: enter notifyDisconnectCompleted +ALL failCause="
-                            + DataFailCause.toString(mDcFailCause));
-                }
-                notifyDisconnectCompleted(mDisconnectParams, true);
-            }
-            if (mDisconnectParams == null && mConnectionParams == null
-                    && mDcFailCause != DataFailCause.NONE) {
-                if (DBG) {
-                    log("DcInactiveState: enter notifyAllDisconnectCompleted failCause="
-                            + DataFailCause.toString(mDcFailCause));
-                }
-                notifyAllWithEvent(null, DctConstants.EVENT_DISCONNECT_DONE,
-                        DataFailCause.toString(mDcFailCause));
-            }
-
-            // Remove ourselves from cid mapping, before clearSettings
-            mDcController.removeActiveDcByCid(DataConnection.this);
-
-            // For the first time entering here (idle state before setup), do not notify
-            // disconnected state. Only notify data connection disconnected for data that is
-            // actually moving from disconnecting to disconnected, or setup failed. In both cases,
-            // APN setting will not be null.
-            if (mApnSetting != null) {
-                notifyDataConnectionState();
-            }
-            clearSettings();
-        }
-
-        @Override
-        public void exit() {
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            switch (msg.what) {
-                case EVENT_RESET:
-                case EVENT_REEVALUATE_RESTRICTED_STATE:
-                    if (DBG) {
-                        log("DcInactiveState: msg.what=" + getWhatToString(msg.what)
-                                + ", ignore we're already done");
-                    }
-                    return HANDLED;
-                case EVENT_CONNECT:
-                    if (DBG) log("DcInactiveState: mag.what=EVENT_CONNECT");
-                    ConnectionParams cp = (ConnectionParams) msg.obj;
-
-                    if (!initConnection(cp)) {
-                        log("DcInactiveState: msg.what=EVENT_CONNECT initConnection failed");
-                        notifyConnectCompleted(cp, DataFailCause.UNACCEPTABLE_NETWORK_PARAMETER,
-                                DataCallResponse.HANDOVER_FAILURE_MODE_UNKNOWN, false);
-                        transitionTo(mInactiveState);
-                        return HANDLED;
-                    }
-
-                    int cause = connect(cp);
-                    if (cause != DataFailCause.NONE) {
-                        log("DcInactiveState: msg.what=EVENT_CONNECT connect failed");
-                        notifyConnectCompleted(cp, cause,
-                                DataCallResponse.HANDOVER_FAILURE_MODE_UNKNOWN, false);
-                        transitionTo(mInactiveState);
-                        return HANDLED;
-                    }
-
-                    if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
-                        mSubId = cp.mSubId;
-                    }
-
-                    transitionTo(mActivatingState);
-                    return HANDLED;
-                case EVENT_DISCONNECT:
-                    if (DBG) log("DcInactiveState: msg.what=EVENT_DISCONNECT");
-                    notifyDisconnectCompleted((DisconnectParams)msg.obj, false);
-                    return HANDLED;
-                case EVENT_DISCONNECT_ALL:
-                    if (DBG) log("DcInactiveState: msg.what=EVENT_DISCONNECT_ALL");
-                    notifyDisconnectCompleted((DisconnectParams)msg.obj, false);
-                    return HANDLED;
-                default:
-                    if (VDBG) {
-                        log("DcInactiveState not handled msg.what=" + getWhatToString(msg.what));
-                    }
-                    return NOT_HANDLED;
-            }
-        }
-    }
-    private DcInactiveState mInactiveState = new DcInactiveState();
-
-    /**
-     * The state machine is activating a connection.
-     */
-    private class DcActivatingState extends State {
-        @Override
-        public void enter() {
-            int apnTypeBitmask = getApnTypeBitmask();
-            TelephonyStatsLog.write(TelephonyStatsLog.MOBILE_CONNECTION_STATE_CHANGED,
-                    TelephonyStatsLog.MOBILE_CONNECTION_STATE_CHANGED__STATE__ACTIVATING,
-                    mPhone.getPhoneId(), mId, apnTypeBitmask, canHandleDefault());
-            setHandoverState(HANDOVER_STATE_IDLE);
-            // restricted evaluation depends on network requests from apnContext. The evaluation
-            // should happen once entering connecting state rather than active state because it's
-            // possible that restricted network request can be released during the connecting window
-            // and if we wait for connection established, then we might mistakenly
-            // consider it as un-restricted. ConnectivityService then will immediately
-            // tear down the connection through networkAgent unwanted callback if all requests for
-            // this connection are going away.
-            mRestrictedNetworkOverride = shouldRestrictNetwork();
-
-            CarrierPrivilegesTracker carrierPrivTracker = mPhone.getCarrierPrivilegesTracker();
-            if (carrierPrivTracker != null) {
-                carrierPrivTracker.registerCarrierPrivilegesListener(
-                            getHandler(), EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED, null);
-            }
-            notifyDataConnectionState();
-            mDataCallSessionStats.onSetupDataCall(apnTypeBitmask);
-        }
-        @Override
-        public boolean processMessage(Message msg) {
-            boolean retVal;
-            AsyncResult ar;
-            ConnectionParams cp;
-
-            if (DBG) log("DcActivatingState: msg=" + msgToString(msg));
-            switch (msg.what) {
-                case EVENT_DATA_CONNECTION_DRS_OR_RAT_CHANGED:
-                case EVENT_CONNECT:
-                    // Activating can't process until we're done.
-                    deferMessage(msg);
-                    retVal = HANDLED;
-                    break;
-
-                case EVENT_SETUP_DATA_CONNECTION_DONE:
-                    cp = (ConnectionParams) msg.obj;
-
-                    DataCallResponse dataCallResponse =
-                            msg.getData().getParcelable(DataServiceManager.DATA_CALL_RESPONSE);
-                    SetupResult result = onSetupConnectionCompleted(msg.arg1, dataCallResponse, cp);
-                    if (result != SetupResult.ERROR_STALE) {
-                        if (mConnectionParams != cp) {
-                            loge("DcActivatingState: WEIRD mConnectionsParams:"+ mConnectionParams
-                                    + " != cp:" + cp);
-                        }
-                    }
-                    if (DBG) {
-                        log("DcActivatingState onSetupConnectionCompleted result=" + result
-                                + " dc=" + DataConnection.this);
-                    }
-                    ApnContext.requestLog(
-                            cp.mApnContext, "onSetupConnectionCompleted result=" + result);
-                    switch (result) {
-                        case SUCCESS:
-                            // All is well
-                            mDcFailCause = DataFailCause.NONE;
-                            transitionTo(mActiveState);
-                            break;
-                        case ERROR_RADIO_NOT_AVAILABLE:
-                            // Vendor ril rejected the command and didn't connect.
-                            // Transition to inactive but send notifications after
-                            // we've entered the mInactive state.
-                            mInactiveState.setEnterNotificationParams(cp, result.mFailCause,
-                                    DataCallResponse.HANDOVER_FAILURE_MODE_UNKNOWN);
-                            transitionTo(mInactiveState);
-                            break;
-                        case ERROR_DUPLICATE_CID:
-                            // TODO (b/180988471): Properly handle the case when an existing cid is
-                            // returned by tearing down the network agent if enterprise changed.
-                            long retry = RetryManager.NO_SUGGESTED_RETRY_DELAY;
-                            if (cp.mApnContext != null) {
-                                retry = RetryManager.NO_RETRY;
-                                mDct.getDataThrottler().setRetryTime(
-                                        cp.mApnContext.getApnTypeBitmask(),
-                                        retry, DcTracker.REQUEST_TYPE_NORMAL);
-                            }
-                            String logStr = "DcActivatingState: "
-                                    + DataFailCause.toString(result.mFailCause)
-                                    + " retry=" + retry;
-                            if (DBG) log(logStr);
-                            ApnContext.requestLog(cp.mApnContext, logStr);
-                            mInactiveState.setEnterNotificationParams(cp, result.mFailCause,
-                                    DataCallResponse.HANDOVER_FAILURE_MODE_UNKNOWN);
-                            transitionTo(mInactiveState);
-                            break;
-                        case ERROR_NO_DEFAULT_CONNECTION:
-                            // TODO (b/180988471): Properly handle the case when a default data
-                            // connection doesn't exist (tear down connection and retry).
-                            // Currently, this just tears down the connection without retry.
-                            if (DBG) log("DcActivatingState: NO_DEFAULT_DATA");
-                        case ERROR_INVALID_ARG:
-                            // The addresses given from the RIL are bad
-                            tearDownData(cp);
-                            transitionTo(mDisconnectingErrorCreatingConnection);
-                            break;
-                        case ERROR_DATA_SERVICE_SPECIFIC_ERROR:
-
-                            // Retrieve the suggested retry delay from the modem and save it.
-                            // If the modem want us to retry the current APN again, it will
-                            // suggest a positive delay value (in milliseconds). Otherwise we'll get
-                            // NO_SUGGESTED_RETRY_DELAY here.
-
-                            long delay = getSuggestedRetryDelay(dataCallResponse);
-                            long retryTime = RetryManager.NO_SUGGESTED_RETRY_DELAY;
-                            if (delay == RetryManager.NO_RETRY) {
-                                retryTime = RetryManager.NO_RETRY;
-                            } else if (delay >= 0) {
-                                retryTime = SystemClock.elapsedRealtime() + delay;
-                            }
-                            int newRequestType = DcTracker.calculateNewRetryRequestType(
-                                    mHandoverFailureMode, cp.mRequestType, mDcFailCause);
-                            mDct.getDataThrottler().setRetryTime(getApnTypeBitmask(),
-                                    retryTime, newRequestType);
-
-                            String str = "DcActivatingState: ERROR_DATA_SERVICE_SPECIFIC_ERROR "
-                                    + " delay=" + delay
-                                    + " result=" + result
-                                    + " result.isRadioRestartFailure="
-                                    + DataFailCause.isRadioRestartFailure(mPhone.getContext(),
-                                    result.mFailCause, mPhone.getSubId())
-                                    + " isPermanentFailure=" +
-                                    mDct.isPermanentFailure(result.mFailCause);
-                            if (DBG) log(str);
-                            ApnContext.requestLog(cp.mApnContext, str);
-
-                            // Save the cause. DcTracker.onDataSetupComplete will check this
-                            // failure cause and determine if we need to retry this APN later
-                            // or not.
-                            mInactiveState.setEnterNotificationParams(cp, result.mFailCause,
-                                    dataCallResponse != null
-                                            ? dataCallResponse.getHandoverFailureMode()
-                                            : DataCallResponse.HANDOVER_FAILURE_MODE_UNKNOWN);
-                            transitionTo(mInactiveState);
-                            break;
-                        case ERROR_STALE:
-                            loge("DcActivatingState: stale EVENT_SETUP_DATA_CONNECTION_DONE"
-                                    + " tag:" + cp.mTag + " != mTag:" + mTag);
-                            break;
-                        default:
-                            throw new RuntimeException("Unknown SetupResult, should not happen");
-                    }
-                    retVal = HANDLED;
-                    mDataCallSessionStats
-                            .onSetupDataCallResponse(dataCallResponse,
-                                    ServiceState.rilRadioTechnologyToNetworkType(cp.mRilRat),
-                                    getApnTypeBitmask(), mApnSetting.getProtocol(),
-                                    result.mFailCause);
-                    break;
-                case EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED:
-                    AsyncResult asyncResult = (AsyncResult) msg.obj;
-                    int[] administratorUids = (int[]) asyncResult.result;
-                    mAdministratorUids = Arrays.copyOf(administratorUids, administratorUids.length);
-                    retVal = HANDLED;
-                    break;
-                case EVENT_START_HANDOVER_ON_TARGET:
-                    //called after startHandover on target transport
-                    ((Consumer<Boolean>) msg.obj).accept(true /* is in correct state*/);
-                    retVal = HANDLED;
-                    break;
-                case EVENT_CANCEL_HANDOVER:
-                    transitionTo(mInactiveState);
-                    retVal = HANDLED;
-                    break;
-                default:
-                    if (VDBG) {
-                        log("DcActivatingState not handled msg.what=" +
-                                getWhatToString(msg.what) + " RefCount=" + mApnContexts.size());
-                    }
-                    retVal = NOT_HANDLED;
-                    break;
-            }
-            return retVal;
-        }
-    }
-    private DcActivatingState mActivatingState = new DcActivatingState();
-
-    /**
-     * The state machine is connected, expecting an EVENT_DISCONNECT.
-     */
-    private class DcActiveState extends State {
-
-        @Override public void enter() {
-            if (DBG) log("DcActiveState: enter dc=" + DataConnection.this);
-            TelephonyStatsLog.write(TelephonyStatsLog.MOBILE_CONNECTION_STATE_CHANGED,
-                    TelephonyStatsLog.MOBILE_CONNECTION_STATE_CHANGED__STATE__ACTIVE,
-                    mPhone.getPhoneId(), mId, getApnTypeBitmask(), canHandleDefault());
-            // If we were retrying there maybe more than one, otherwise they'll only be one.
-            notifyAllWithEvent(null, DctConstants.EVENT_DATA_SETUP_COMPLETE,
-                    Phone.REASON_CONNECTED);
-
-            mPhone.getCallTracker().registerForVoiceCallStarted(getHandler(),
-                    DataConnection.EVENT_DATA_CONNECTION_VOICE_CALL_STARTED, null);
-            mPhone.getCallTracker().registerForVoiceCallEnded(getHandler(),
-                    DataConnection.EVENT_DATA_CONNECTION_VOICE_CALL_ENDED, null);
-
-            // If the EVENT_CONNECT set the current max retry restore it here
-            // if it didn't then this is effectively a NOP.
-            mDcController.addActiveDcByCid(DataConnection.this);
-
-            updateTcpBufferSizes(mRilRat);
-            updateLinkBandwidthsFromCarrierConfig(mRilRat);
-
-            final NetworkAgentConfig.Builder configBuilder = new NetworkAgentConfig.Builder();
-            configBuilder.setLegacyType(ConnectivityManager.TYPE_MOBILE);
-            configBuilder.setLegacyTypeName(NETWORK_TYPE);
-            int networkType = getNetworkType();
-            configBuilder.setLegacySubType(networkType);
-            configBuilder.setLegacySubTypeName(TelephonyManager.getNetworkTypeName(networkType));
-            configBuilder.setLegacyExtraInfo(mApnSetting.getApnName());
-            final CarrierSignalAgent carrierSignalAgent = mPhone.getCarrierSignalAgent();
-            if (carrierSignalAgent.hasRegisteredReceivers(TelephonyManager
-                    .ACTION_CARRIER_SIGNAL_REDIRECTED)) {
-                // carrierSignal Receivers will place the carrier-specific provisioning notification
-                configBuilder.setProvisioningNotificationEnabled(false);
-            }
-
-            final String subscriberId = mPhone.getSubscriberId();
-            if (!TextUtils.isEmpty(subscriberId)) {
-                configBuilder.setSubscriberId(subscriberId);
-            }
-
-            // set skip464xlat if it is not default otherwise
-            if (shouldSkip464Xlat()) {
-                configBuilder.setNat64DetectionEnabled(false);
-            }
-
-            mUnmeteredUseOnly = isUnmeteredUseOnly();
-            mMmsUseOnly = isMmsUseOnly();
-            mEnterpriseUse = isEnterpriseUse();
-
-            if (DBG) {
-                log("mRestrictedNetworkOverride = " + mRestrictedNetworkOverride
-                        + ", mUnmeteredUseOnly = " + mUnmeteredUseOnly
-                        + ", mMmsUseOnly = " + mMmsUseOnly
-                        + ", mEnterpriseUse = " + mEnterpriseUse);
-            }
-
-            // Always register a VcnNetworkPolicyChangeListener, regardless of whether this is a
-            // handover
-            // or new Network.
-            mVcnManager.addVcnNetworkPolicyChangeListener(
-                    new HandlerExecutor(getHandler()), mVcnPolicyChangeListener);
-
-            if (mConnectionParams != null
-                    && mConnectionParams.mRequestType == REQUEST_TYPE_HANDOVER) {
-                // If this is a data setup for handover, we need to reuse the existing network agent
-                // instead of creating a new one. This should be transparent to connectivity
-                // service.
-                DcTracker dcTracker = mPhone.getDcTracker(getHandoverSourceTransport());
-                DataConnection dc = dcTracker.getDataConnectionByApnType(
-                        mConnectionParams.mApnContext.getApnType());
-                // It's possible that the source data connection has been disconnected by the modem
-                // already. If not, set its handover state to completed.
-                if (dc != null) {
-                    // Transfer network agent from the original data connection as soon as the
-                    // new handover data connection is connected.
-                    dc.setHandoverState(HANDOVER_STATE_COMPLETED);
-                }
-
-                if (mHandoverSourceNetworkAgent != null) {
-                    String logStr = "Transfer network agent " + mHandoverSourceNetworkAgent.getTag()
-                            + " successfully.";
-                    log(logStr);
-                    mHandoverLocalLog.log(logStr);
-                    mNetworkAgent = mHandoverSourceNetworkAgent;
-                    mNetworkAgent.acquireOwnership(DataConnection.this, mTransportType);
-
-                    // TODO: Should evaluate mDisabledApnTypeBitMask again after handover. We don't
-                    // do it now because connectivity service does not support dynamically removing
-                    // immutable capabilities.
-
-                    mNetworkAgent.updateLegacySubtype(DataConnection.this);
-                    // Update the capability after handover
-                    mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities(),
-                            DataConnection.this);
-                    mNetworkAgent.sendLinkProperties(mLinkProperties, DataConnection.this);
-                    mHandoverSourceNetworkAgent = null;
-                } else {
-                    String logStr = "Failed to get network agent from original data connection";
-                    loge(logStr);
-                    mHandoverLocalLog.log(logStr);
-                    return;
-                }
-            } else {
-                mScore = calculateScore();
-                final NetworkFactory factory = PhoneFactory.getNetworkFactory(
-                        mPhone.getPhoneId());
-                final NetworkProvider provider = (null == factory) ? null : factory.getProvider();
-
-                mDisabledApnTypeBitMask |= getDisallowedApnTypes();
-                updateLinkPropertiesHttpProxy();
-                mNetworkAgent = new DcNetworkAgent(DataConnection.this, mPhone, mScore,
-                        configBuilder.build(), provider, mTransportType);
-
-                VcnNetworkPolicyResult policyResult =
-                        mVcnManager.applyVcnNetworkPolicy(
-                                getNetworkCapabilities(), getLinkProperties());
-                if (policyResult.isTeardownRequested()) {
-                    tearDownAll(
-                            Phone.REASON_VCN_REQUESTED_TEARDOWN,
-                            DcTracker.RELEASE_TYPE_DETACH,
-                            null /* onCompletedMsg */);
-                } else {
-                    // All network agents start out in CONNECTING mode, but DcNetworkAgents are
-                    // created when the network is already connected. Hence, send the connected
-                    // notification immediately.
-                    mNetworkAgent.markConnected();
-                }
-
-                // The network agent is always created with NOT_SUSPENDED capability, but the
-                // network might be already out of service (or voice call is ongoing) just right
-                // before data connection is created. Connectivity service would not allow a network
-                // created with suspended state, so we create a non-suspended network first, and
-                // then immediately evaluate the suspended state.
-                sendMessage(obtainMessage(EVENT_UPDATE_SUSPENDED_STATE));
-            }
-
-            // The qos parameters are set when the call is connected
-            syncQosToNetworkAgent();
-
-            if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
-                mPhone.mCi.registerForNattKeepaliveStatus(
-                        getHandler(), DataConnection.EVENT_KEEPALIVE_STATUS, null);
-                mPhone.mCi.registerForLceInfo(
-                        getHandler(), DataConnection.EVENT_LINK_CAPACITY_CHANGED, null);
-            }
-            notifyDataConnectionState();
-            TelephonyMetrics.getInstance().writeRilDataCallEvent(mPhone.getPhoneId(),
-                    mCid, getApnTypeBitmask(), RilDataCall.State.CONNECTED);
-        }
-
-        @Override
-        public void exit() {
-            if (DBG) log("DcActiveState: exit dc=" + this);
-            mPhone.getCallTracker().unregisterForVoiceCallStarted(getHandler());
-            mPhone.getCallTracker().unregisterForVoiceCallEnded(getHandler());
-
-            if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
-                mPhone.mCi.unregisterForNattKeepaliveStatus(getHandler());
-                mPhone.mCi.unregisterForLceInfo(getHandler());
-            }
-
-            // If we are still owning this agent, then we should inform connectivity service the
-            // data connection is disconnected. There is one exception that we shouldn't unregister,
-            // which is when IWLAN handover is ongoing. Instead of unregistering, the agent will
-            // be transferred to the new data connection on the other transport.
-            if (mNetworkAgent != null) {
-                syncQosToNetworkAgent();
-                if (mHandoverState == HANDOVER_STATE_IDLE) {
-                    mNetworkAgent.unregister(DataConnection.this);
-                }
-                mNetworkAgent.releaseOwnership(DataConnection.this);
-            }
-            mNetworkAgent = null;
-
-            TelephonyMetrics.getInstance().writeRilDataCallEvent(mPhone.getPhoneId(),
-                    mCid, getApnTypeBitmask(), RilDataCall.State.DISCONNECTED);
-
-            mVcnManager.removeVcnNetworkPolicyChangeListener(mVcnPolicyChangeListener);
-
-            CarrierPrivilegesTracker carrierPrivTracker = mPhone.getCarrierPrivilegesTracker();
-            if (carrierPrivTracker != null) {
-                carrierPrivTracker.unregisterCarrierPrivilegesListener(getHandler());
-            }
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            boolean retVal;
-
-            switch (msg.what) {
-                case EVENT_CONNECT: {
-                    ConnectionParams cp = (ConnectionParams) msg.obj;
-                    // either add this new apn context to our set or
-                    // update the existing cp with the latest connection generation number
-                    mApnContexts.put(cp.mApnContext, cp);
-                    // TODO (b/118347948): evaluate if it's still needed after assigning
-                    // different scores to different Cellular network.
-                    mDisabledApnTypeBitMask &= ~cp.mApnContext.getApnTypeBitmask();
-                    mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities(),
-                            DataConnection.this);
-                    if (DBG) {
-                        log("DcActiveState: EVENT_CONNECT cp=" + cp + " dc=" + DataConnection.this);
-                    }
-                    notifyConnectCompleted(cp, DataFailCause.NONE,
-                            DataCallResponse.HANDOVER_FAILURE_MODE_UNKNOWN, false);
-                    retVal = HANDLED;
-                    break;
-                }
-                case EVENT_DISCONNECT: {
-                    DisconnectParams dp = (DisconnectParams) msg.obj;
-                    if (DBG) {
-                        log("DcActiveState: EVENT_DISCONNECT dp=" + dp
-                                + " dc=" + DataConnection.this);
-                    }
-                    if (mApnContexts.containsKey(dp.mApnContext)) {
-                        if (DBG) {
-                            log("DcActiveState msg.what=EVENT_DISCONNECT RefCount="
-                                    + mApnContexts.size());
-                        }
-
-                        if (mApnContexts.size() == 1) {
-                            mApnContexts.clear();
-                            mDisconnectParams = dp;
-                            mConnectionParams = null;
-                            dp.mTag = mTag;
-                            tearDownData(dp);
-                            transitionTo(mDisconnectingState);
-                        } else {
-                            mApnContexts.remove(dp.mApnContext);
-                            // TODO (b/118347948): evaluate if it's still needed after assigning
-                            // different scores to different Cellular network.
-                            mDisabledApnTypeBitMask |= dp.mApnContext.getApnTypeBitmask();
-                            mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities(),
-                                    DataConnection.this);
-                            notifyDisconnectCompleted(dp, false);
-                        }
-                    } else {
-                        log("DcActiveState ERROR no such apnContext=" + dp.mApnContext
-                                + " in this dc=" + DataConnection.this);
-                        notifyDisconnectCompleted(dp, false);
-                    }
-                    retVal = HANDLED;
-                    break;
-                }
-                case EVENT_DISCONNECT_ALL: {
-                    if (DBG) {
-                        log("DcActiveState EVENT_DISCONNECT clearing apn contexts,"
-                                + " dc=" + DataConnection.this);
-                    }
-                    DisconnectParams dp = (DisconnectParams) msg.obj;
-                    mDisconnectParams = dp;
-                    mConnectionParams = null;
-                    dp.mTag = mTag;
-                    tearDownData(dp);
-                    transitionTo(mDisconnectingState);
-                    retVal = HANDLED;
-                    break;
-                }
-                case EVENT_LOST_CONNECTION: {
-                    if (DBG) {
-                        log("DcActiveState EVENT_LOST_CONNECTION dc=" + DataConnection.this);
-                    }
-
-                    mInactiveState.setEnterNotificationParams(DataFailCause.LOST_CONNECTION);
-                    transitionTo(mInactiveState);
-                    retVal = HANDLED;
-                    break;
-                }
-                case EVENT_DATA_CONNECTION_DRS_OR_RAT_CHANGED: {
-                    AsyncResult ar = (AsyncResult) msg.obj;
-                    Pair<Integer, Integer> drsRatPair = (Pair<Integer, Integer>) ar.result;
-                    mDataRegState = drsRatPair.first;
-                    updateTcpBufferSizes(drsRatPair.second);
-                    if (isBandwidthSourceKey(DctConstants.BANDWIDTH_SOURCE_CARRIER_CONFIG_KEY)) {
-                        updateLinkBandwidthsFromCarrierConfig(drsRatPair.second);
-                    }
-                    mRilRat = drsRatPair.second;
-                    if (DBG) {
-                        log("DcActiveState: EVENT_DATA_CONNECTION_DRS_OR_RAT_CHANGED"
-                                + " drs=" + mDataRegState
-                                + " mRilRat=" + mRilRat);
-                    }
-                    updateSuspendState();
-                    if (mNetworkAgent != null) {
-                        mNetworkAgent.updateLegacySubtype(DataConnection.this);
-                        // The new suspended state will be passed through connectivity service
-                        // through NET_CAPABILITY_NOT_SUSPENDED.
-                        mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities(),
-                                DataConnection.this);
-                        mNetworkAgent.sendLinkProperties(mLinkProperties, DataConnection.this);
-                    }
-                    retVal = HANDLED;
-                    mDataCallSessionStats.onDrsOrRatChanged(
-                            ServiceState.rilRadioTechnologyToNetworkType(mRilRat));
-                    break;
-                }
-                case EVENT_NR_FREQUENCY_CHANGED:
-                    // fallthrough
-                case EVENT_CARRIER_CONFIG_LINK_BANDWIDTHS_CHANGED:
-                    if (isBandwidthSourceKey(DctConstants.BANDWIDTH_SOURCE_CARRIER_CONFIG_KEY)) {
-                        updateLinkBandwidthsFromCarrierConfig(mRilRat);
-                    }
-                    if (mNetworkAgent != null) {
-                        mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities(),
-                                DataConnection.this);
-                    }
-                    retVal = HANDLED;
-                    break;
-                case EVENT_DATA_CONNECTION_METEREDNESS_CHANGED:
-                    boolean isUnmetered = (boolean) msg.obj;
-                    if (isUnmetered == mUnmeteredOverride) {
-                        retVal = HANDLED;
-                        break;
-                    }
-                    mUnmeteredOverride = isUnmetered;
-                    if (mNetworkAgent != null) {
-                        mNetworkAgent.updateLegacySubtype(DataConnection.this);
-                        mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities(),
-                                DataConnection.this);
-                    }
-                    retVal = HANDLED;
-                    break;
-                case EVENT_DATA_CONNECTION_CONGESTEDNESS_CHANGED:
-                    boolean isCongested = (boolean) msg.obj;
-                    if (isCongested == mCongestedOverride) {
-                        retVal = HANDLED;
-                        break;
-                    }
-                    mCongestedOverride = isCongested;
-                    if (mNetworkAgent != null) {
-                        mNetworkAgent.updateLegacySubtype(DataConnection.this);
-                        mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities(),
-                                DataConnection.this);
-                    }
-                    retVal = HANDLED;
-                    break;
-                case EVENT_DATA_CONNECTION_ROAM_ON:
-                case EVENT_DATA_CONNECTION_ROAM_OFF: {
-                    if (mNetworkAgent != null) {
-                        mNetworkAgent.updateLegacySubtype(DataConnection.this);
-                        mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities(),
-                                DataConnection.this);
-                    }
-                    retVal = HANDLED;
-                    break;
-                }
-                case EVENT_DATA_CONNECTION_VOICE_CALL_STARTED:
-                case EVENT_DATA_CONNECTION_VOICE_CALL_ENDED:
-                case EVENT_CSS_INDICATOR_CHANGED:
-                case EVENT_UPDATE_SUSPENDED_STATE: {
-                    updateSuspendState();
-                    if (mNetworkAgent != null) {
-                        mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities(),
-                                DataConnection.this);
-                    }
-                    retVal = HANDLED;
-                    break;
-                }
-                case EVENT_BW_REFRESH_RESPONSE: {
-                    AsyncResult ar = (AsyncResult)msg.obj;
-                    if (ar.exception != null) {
-                        log("EVENT_BW_REFRESH_RESPONSE: error ignoring, e=" + ar.exception);
-                    } else {
-                        if (isBandwidthSourceKey(DctConstants.BANDWIDTH_SOURCE_MODEM_KEY)) {
-                            updateLinkBandwidthsFromModem((List<LinkCapacityEstimate>) ar.result);
-                        }
-                    }
-                    retVal = HANDLED;
-                    break;
-                }
-                case EVENT_KEEPALIVE_START_REQUEST: {
-                    KeepalivePacketData pkt = (KeepalivePacketData) msg.obj;
-                    int slotId = msg.arg1;
-                    int intervalMillis = msg.arg2 * 1000;
-                    if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
-                        mPhone.mCi.startNattKeepalive(
-                                DataConnection.this.mCid, pkt, intervalMillis,
-                                DataConnection.this.obtainMessage(
-                                        EVENT_KEEPALIVE_STARTED, slotId, 0, null));
-                    } else {
-                        // We currently do not support NATT Keepalive requests using the
-                        // DataService API, so unless the request is WWAN (always bound via
-                        // the CommandsInterface), the request cannot be honored.
-                        //
-                        // TODO: b/72331356 to add support for Keepalive to the DataService
-                        // so that keepalive requests can be handled (if supported) by the
-                        // underlying transport.
-                        if (mNetworkAgent != null) {
-                            mNetworkAgent.sendSocketKeepaliveEvent(
-                                    msg.arg1, SocketKeepalive.ERROR_INVALID_NETWORK);
-                        }
-                    }
-                    retVal = HANDLED;
-                    break;
-                }
-                case EVENT_KEEPALIVE_STOP_REQUEST: {
-                    int slotId = msg.arg1;
-                    int handle = mNetworkAgent.keepaliveTracker.getHandleForSlot(slotId);
-                    if (handle < 0) {
-                        loge("No slot found for stopSocketKeepalive! " + slotId);
-                        mNetworkAgent.sendSocketKeepaliveEvent(
-                                slotId, SocketKeepalive.ERROR_NO_SUCH_SLOT);
-                        retVal = HANDLED;
-                        break;
-                    } else {
-                        logd("Stopping keepalive with handle: " + handle);
-                    }
-
-                    mPhone.mCi.stopNattKeepalive(
-                            handle, DataConnection.this.obtainMessage(
-                                    EVENT_KEEPALIVE_STOPPED, handle, slotId, null));
-                    retVal = HANDLED;
-                    break;
-                }
-                case EVENT_KEEPALIVE_STARTED: {
-                    AsyncResult ar = (AsyncResult) msg.obj;
-                    final int slot = msg.arg1;
-                    if (ar.exception != null || ar.result == null) {
-                        loge("EVENT_KEEPALIVE_STARTED: error starting keepalive, e="
-                                + ar.exception);
-                        mNetworkAgent.sendSocketKeepaliveEvent(
-                                slot, SocketKeepalive.ERROR_HARDWARE_ERROR);
-                    } else {
-                        KeepaliveStatus ks = (KeepaliveStatus) ar.result;
-                        if (ks == null) {
-                            loge("Null KeepaliveStatus received!");
-                        } else {
-                            mNetworkAgent.keepaliveTracker.handleKeepaliveStarted(slot, ks);
-                        }
-                    }
-                    retVal = HANDLED;
-                    break;
-                }
-                case EVENT_KEEPALIVE_STATUS: {
-                    AsyncResult ar = (AsyncResult) msg.obj;
-                    if (ar.exception != null) {
-                        loge("EVENT_KEEPALIVE_STATUS: error in keepalive, e=" + ar.exception);
-                        // We have no way to notify connectivity in this case.
-                    }
-                    if (ar.result != null) {
-                        KeepaliveStatus ks = (KeepaliveStatus) ar.result;
-                        mNetworkAgent.keepaliveTracker.handleKeepaliveStatus(ks);
-                    }
-
-                    retVal = HANDLED;
-                    break;
-                }
-                case EVENT_KEEPALIVE_STOPPED: {
-                    AsyncResult ar = (AsyncResult) msg.obj;
-                    final int handle = msg.arg1;
-                    final int slotId = msg.arg2;
-
-                    if (ar.exception != null) {
-                        loge("EVENT_KEEPALIVE_STOPPED: error stopping keepalive for handle="
-                                + handle + " e=" + ar.exception);
-                        mNetworkAgent.keepaliveTracker.handleKeepaliveStatus(
-                                new KeepaliveStatus(KeepaliveStatus.ERROR_UNKNOWN));
-                    } else {
-                        log("Keepalive Stop Requested for handle=" + handle);
-                        mNetworkAgent.keepaliveTracker.handleKeepaliveStatus(
-                                new KeepaliveStatus(
-                                        handle, KeepaliveStatus.STATUS_INACTIVE));
-                    }
-                    retVal = HANDLED;
-                    break;
-                }
-                case EVENT_LINK_CAPACITY_CHANGED: {
-                    AsyncResult ar = (AsyncResult) msg.obj;
-                    if (ar.exception != null) {
-                        loge("EVENT_LINK_CAPACITY_CHANGED e=" + ar.exception);
-                    } else {
-                        if (isBandwidthSourceKey(DctConstants.BANDWIDTH_SOURCE_MODEM_KEY)) {
-                            updateLinkBandwidthsFromModem((List<LinkCapacityEstimate>) ar.result);
-                        }
-                    }
-                    retVal = HANDLED;
-                    break;
-                }
-                case EVENT_LINK_BANDWIDTH_ESTIMATOR_UPDATE: {
-                    AsyncResult ar = (AsyncResult) msg.obj;
-                    if (ar.exception != null) {
-                        loge("EVENT_LINK_BANDWIDTH_ESTIMATOR_UPDATE e=" + ar.exception);
-                    } else {
-                        Pair<Integer, Integer> pair = (Pair<Integer, Integer>) ar.result;
-                        if (isBandwidthSourceKey(
-                                DctConstants.BANDWIDTH_SOURCE_BANDWIDTH_ESTIMATOR_KEY)) {
-                            updateLinkBandwidthsFromBandwidthEstimator(pair.first, pair.second);
-                        }
-                    }
-                    retVal = HANDLED;
-                    break;
-                }
-                case EVENT_REEVALUATE_RESTRICTED_STATE: {
-                    // If the network was restricted, and now it does not need to be restricted
-                    // anymore, we should add the NET_CAPABILITY_NOT_RESTRICTED capability.
-                    if (mRestrictedNetworkOverride && !shouldRestrictNetwork()) {
-                        if (DBG) {
-                            log("Data connection becomes not-restricted. dc=" + this);
-                        }
-                        // Note we only do this when network becomes non-restricted. When a
-                        // non-restricted becomes restricted (e.g. users disable data, or turn off
-                        // data roaming), DCT will explicitly tear down the networks (because
-                        // connectivity service does not support force-close TCP connections today).
-                        // Also note that NET_CAPABILITY_NOT_RESTRICTED is an immutable capability
-                        // (see {@link NetworkCapabilities}) once we add it to the network, we can't
-                        // remove it through the entire life cycle of the connection.
-                        mRestrictedNetworkOverride = false;
-                        mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities(),
-                                DataConnection.this);
-                    }
-
-                    // If the data does need to be unmetered use only (e.g. users turn on data, or
-                    // device is not roaming anymore assuming data roaming is off), then we can
-                    // dynamically add those metered APN type capabilities back. (But not the
-                    // other way around because most of the APN-type capabilities are immutable
-                    // capabilities.)
-                    if (mUnmeteredUseOnly && !isUnmeteredUseOnly()) {
-                        mUnmeteredUseOnly = false;
-                        mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities(),
-                                DataConnection.this);
-                    }
-
-                    mMmsUseOnly = isMmsUseOnly();
-
-                    retVal = HANDLED;
-                    break;
-                }
-                case EVENT_REEVALUATE_DATA_CONNECTION_PROPERTIES: {
-                    // Update other properties like link properties if needed in future.
-                    updateScore();
-                    retVal = HANDLED;
-                    break;
-                }
-                case EVENT_NR_STATE_CHANGED: {
-                    updateTcpBufferSizes(mRilRat);
-                    if (isBandwidthSourceKey(DctConstants.BANDWIDTH_SOURCE_CARRIER_CONFIG_KEY)) {
-                        updateLinkBandwidthsFromCarrierConfig(mRilRat);
-                    }
-                    if (mNetworkAgent != null) {
-                        mNetworkAgent.sendLinkProperties(mLinkProperties, DataConnection.this);
-                        mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities(),
-                                DataConnection.this);
-                    }
-                    retVal = HANDLED;
-                    break;
-                }
-                case EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED:
-                    AsyncResult asyncResult = (AsyncResult) msg.obj;
-                    int[] administratorUids = (int[]) asyncResult.result;
-                    mAdministratorUids = Arrays.copyOf(administratorUids, administratorUids.length);
-
-                    // Administrator UIDs changed, so update NetworkAgent with new
-                    // NetworkCapabilities
-                    if (mNetworkAgent != null) {
-                        mNetworkAgent.sendNetworkCapabilities(
-                                getNetworkCapabilities(), DataConnection.this);
-                    }
-                    retVal = HANDLED;
-                    break;
-                case EVENT_START_HANDOVER:  //calls startHandover()
-                    Consumer<Integer> r = (Consumer<Integer>) msg.obj;
-                    r.accept(msg.arg1);
-                    retVal = HANDLED;
-                    break;
-
-                default:
-                    if (VDBG) {
-                        log("DcActiveState not handled msg.what=" + getWhatToString(msg.what));
-                    }
-                    retVal = NOT_HANDLED;
-                    break;
-            }
-            return retVal;
-        }
-    }
-    private DcActiveState mActiveState = new DcActiveState();
-
-    /**
-     * The state machine is disconnecting.
-     */
-    private class DcDisconnectingState extends State {
-        @Override
-        public void enter() {
-            TelephonyStatsLog.write(TelephonyStatsLog.MOBILE_CONNECTION_STATE_CHANGED,
-                    TelephonyStatsLog.MOBILE_CONNECTION_STATE_CHANGED__STATE__DISCONNECTING,
-                    mPhone.getPhoneId(), mId, getApnTypeBitmask(), canHandleDefault());
-            notifyDataConnectionState();
-        }
-        @Override
-        public boolean processMessage(Message msg) {
-            boolean retVal;
-
-            switch (msg.what) {
-                case EVENT_CONNECT:
-                    if (DBG) log("DcDisconnectingState msg.what=EVENT_CONNECT. Defer. RefCount = "
-                            + mApnContexts.size());
-                    deferMessage(msg);
-                    retVal = HANDLED;
-                    break;
-
-                case EVENT_DEACTIVATE_DONE:
-                    DisconnectParams dp = (DisconnectParams) msg.obj;
-
-                    String str = "DcDisconnectingState msg.what=EVENT_DEACTIVATE_DONE RefCount="
-                            + mApnContexts.size();
-
-                    if (DBG) log(str);
-                    ApnContext.requestLog(dp.mApnContext, str);
-
-                    // Clear out existing qos sessions
-                    updateQosParameters(null);
-
-                    if (dp.mTag == mTag) {
-                        // Transition to inactive but send notifications after
-                        // we've entered the mInactive state.
-                        mInactiveState.setEnterNotificationParams(dp);
-                        transitionTo(mInactiveState);
-                    } else {
-                        if (DBG) log("DcDisconnectState stale EVENT_DEACTIVATE_DONE"
-                                + " dp.tag=" + dp.mTag + " mTag=" + mTag);
-                    }
-                    retVal = HANDLED;
-                    break;
-
-                default:
-                    if (VDBG) {
-                        log("DcDisconnectingState not handled msg.what="
-                                + getWhatToString(msg.what));
-                    }
-                    retVal = NOT_HANDLED;
-                    break;
-            }
-            return retVal;
-        }
-    }
-    private DcDisconnectingState mDisconnectingState = new DcDisconnectingState();
-
-    /**
-     * The state machine is disconnecting after an creating a connection.
-     */
-    private class DcDisconnectionErrorCreatingConnection extends State {
-        @Override
-        public void enter() {
-            TelephonyStatsLog.write(TelephonyStatsLog.MOBILE_CONNECTION_STATE_CHANGED,
-                    TelephonyStatsLog
-                            .MOBILE_CONNECTION_STATE_CHANGED__STATE__DISCONNECTION_ERROR_CREATING_CONNECTION,
-                    mPhone.getPhoneId(), mId, getApnTypeBitmask(), canHandleDefault());
-            notifyDataConnectionState();
-        }
-        @Override
-        public boolean processMessage(Message msg) {
-            boolean retVal;
-
-            switch (msg.what) {
-                case EVENT_DEACTIVATE_DONE:
-                    ConnectionParams cp = (ConnectionParams) msg.obj;
-                    if (cp.mTag == mTag) {
-                        String str = "DcDisconnectionErrorCreatingConnection" +
-                                " msg.what=EVENT_DEACTIVATE_DONE";
-                        if (DBG) log(str);
-                        ApnContext.requestLog(cp.mApnContext, str);
-
-                        // Transition to inactive but send notifications after
-                        // we've entered the mInactive state.
-                        mInactiveState.setEnterNotificationParams(cp,
-                                DataFailCause.UNACCEPTABLE_NETWORK_PARAMETER,
-                                DataCallResponse.HANDOVER_FAILURE_MODE_UNKNOWN);
-                        transitionTo(mInactiveState);
-                    } else {
-                        if (DBG) {
-                            log("DcDisconnectionErrorCreatingConnection stale EVENT_DEACTIVATE_DONE"
-                                    + " dp.tag=" + cp.mTag + ", mTag=" + mTag);
-                        }
-                    }
-                    retVal = HANDLED;
-                    break;
-
-                default:
-                    if (VDBG) {
-                        log("DcDisconnectionErrorCreatingConnection not handled msg.what="
-                                + getWhatToString(msg.what));
-                    }
-                    retVal = NOT_HANDLED;
-                    break;
-            }
-            return retVal;
-        }
-    }
-    private DcDisconnectionErrorCreatingConnection mDisconnectingErrorCreatingConnection =
-                new DcDisconnectionErrorCreatingConnection();
-
-    /**
-     * Bring up a connection to the apn and return an AsyncResult in onCompletedMsg.
-     * Used for cellular networks that use Access Point Names (APN) such
-     * as GSM networks.
-     *
-     * @param apnContext is the Access Point Name to bring up a connection to
-     * @param profileId for the connection
-     * @param rilRadioTechnology Radio technology for the data connection
-     * @param onCompletedMsg is sent with its msg.obj as an AsyncResult object.
-     *                       With AsyncResult.userObj set to the original msg.obj,
-     *                       AsyncResult.result = FailCause and AsyncResult.exception = Exception().
-     * @param connectionGeneration used to track a single connection request so disconnects can get
-     *                             ignored if obsolete.
-     * @param requestType Data request type
-     * @param subId the subscription id associated with this data connection.
-     * @param isApnPreferred whether or not the apn is preferred.
-     */
-    public void bringUp(ApnContext apnContext, int profileId, int rilRadioTechnology,
-                        Message onCompletedMsg, int connectionGeneration,
-                        @RequestNetworkType int requestType, int subId, boolean isApnPreferred) {
-        if (DBG) {
-            log("bringUp: apnContext=" + apnContext + " onCompletedMsg=" + onCompletedMsg);
-        }
-
-        if (mApnSetting == null) {
-            mApnSetting = apnContext.getApnSetting();
-        }
-
-        sendMessage(DataConnection.EVENT_CONNECT,
-                new ConnectionParams(apnContext, profileId, rilRadioTechnology, onCompletedMsg,
-                        connectionGeneration, requestType, subId, isApnPreferred));
-    }
-
-    /**
-     * Tear down the connection through the apn on the network.
-     *
-     * @param apnContext APN context
-     * @param reason reason to tear down
-     * @param onCompletedMsg is sent with its msg.obj as an AsyncResult object.
-     *        With AsyncResult.userObj set to the original msg.obj.
-     */
-    public void tearDown(ApnContext apnContext, String reason, Message onCompletedMsg) {
-        if (DBG) {
-            log("tearDown: apnContext=" + apnContext + " reason=" + reason + " onCompletedMsg="
-                    + onCompletedMsg);
-        }
-        sendMessage(DataConnection.EVENT_DISCONNECT,
-                new DisconnectParams(apnContext, reason, DcTracker.RELEASE_TYPE_DETACH,
-                        onCompletedMsg));
-    }
-
-    // ******* "public" interface
-
-    /**
-     * Used for testing purposes.
-     */
-    void tearDownNow() {
-        if (DBG) log("tearDownNow()");
-        sendMessage(obtainMessage(EVENT_TEAR_DOWN_NOW));
-    }
-
-    /**
-     * Tear down the connection through the apn on the network.  Ignores reference count and
-     * and always tears down.
-     *
-     * @param releaseType Data release type
-     * @param onCompletedMsg is sent with its msg.obj as an AsyncResult object.
-     *        With AsyncResult.userObj set to the original msg.obj.
-     */
-    public void tearDownAll(String reason, @ReleaseNetworkType int releaseType,
-                            Message onCompletedMsg) {
-        if (DBG) {
-            log("tearDownAll: reason=" + reason + ", releaseType="
-                    + DcTracker.releaseTypeToString(releaseType));
-        }
-        sendMessage(DataConnection.EVENT_DISCONNECT_ALL,
-                new DisconnectParams(null, reason, releaseType, onCompletedMsg));
-    }
-
-    /**
-     * Reset the data connection to inactive state.
-     */
-    public void reset() {
-        sendMessage(EVENT_RESET);
-        if (DBG) log("reset");
-    }
-
-    /**
-     * Re-evaluate the restricted state. If the restricted data connection does not need to be
-     * restricted anymore, we need to dynamically change the network's capability.
-     */
-    void reevaluateRestrictedState() {
-        sendMessage(EVENT_REEVALUATE_RESTRICTED_STATE);
-        if (DBG) log("reevaluate restricted state");
-    }
-
-    /**
-     * Re-evaluate the data connection properties. For example, it will recalculate data connection
-     * score and update through network agent it if changed.
-     */
-    void reevaluateDataConnectionProperties() {
-        sendMessage(EVENT_REEVALUATE_DATA_CONNECTION_PROPERTIES);
-        if (DBG) log("reevaluate data connection properties");
-    }
-
-    /**
-     * @return The parameters used for initiating a data connection.
-     */
-    public ConnectionParams getConnectionParams() {
-        return mConnectionParams;
-    }
-
-    /**
-     * Update PCSCF addresses
-     *
-     * @param response
-     */
-    public void updatePcscfAddr(DataCallResponse response) {
-        mPcscfAddr = response.getPcscfAddresses().stream()
-                .map(InetAddress::getHostAddress).toArray(String[]::new);
-    }
-
-    /**
-     * @return The list of PCSCF addresses
-     */
-    public String[] getPcscfAddresses() {
-        return mPcscfAddr;
-    }
-
-    /**
-     * Using the result of the SETUP_DATA_CALL determine the retry delay.
-     *
-     * @param response The response from setup data call
-     * @return {@link RetryManager#NO_SUGGESTED_RETRY_DELAY} if not suggested.
-     * {@link RetryManager#NO_RETRY} if retry should not happen. Otherwise the delay in milliseconds
-     * to the next SETUP_DATA_CALL.
-     */
-    private long getSuggestedRetryDelay(DataCallResponse response) {
-        /** According to ril.h
-         * The value < 0 means no value is suggested
-         * The value 0 means retry should be done ASAP.
-         * The value of Long.MAX_VALUE(0x7fffffffffffffff) means no retry.
-         */
-        if (response == null) {
-            return RetryManager.NO_SUGGESTED_RETRY_DELAY;
-        }
-
-        long suggestedRetryTime = response.getRetryDurationMillis();
-
-        // The value < 0 means no value is suggested
-        if (suggestedRetryTime < 0) {
-            if (DBG) log("No suggested retry delay.");
-            return RetryManager.NO_SUGGESTED_RETRY_DELAY;
-        } else if (mPhone.getHalVersion().greaterOrEqual(RIL.RADIO_HAL_VERSION_1_6)
-                && suggestedRetryTime == Long.MAX_VALUE) {
-            if (DBG) log("Network suggested not retrying.");
-            return RetryManager.NO_RETRY;
-        } else if (mPhone.getHalVersion().less(RIL.RADIO_HAL_VERSION_1_6)
-                && suggestedRetryTime == Integer.MAX_VALUE) {
-            if (DBG) log("Network suggested not retrying.");
-            return RetryManager.NO_RETRY;
-        }
-
-        return suggestedRetryTime;
-    }
-
-    public List<ApnContext> getApnContexts() {
-        return new ArrayList<>(mApnContexts.keySet());
-    }
-
-    /**
-     * Return whether there is an ApnContext for the given type in this DataConnection.
-     * @param type APN type to check
-     * @param exclusive true if the given APN type should be the only APN type that exists
-     * @return True if there is an ApnContext for the given type
-     */
-    private boolean isApnContextAttached(@ApnType int type, boolean exclusive) {
-        boolean attached = mApnContexts.keySet().stream()
-                .map(ApnContext::getApnTypeBitmask)
-                .anyMatch(bitmask -> bitmask == type);
-        if (exclusive) {
-            attached &= mApnContexts.size() == 1;
-        }
-        return attached;
-    }
-
-    /** Get the network agent of the data connection */
-    @Nullable
-    DcNetworkAgent getNetworkAgent() {
-        return mNetworkAgent;
-    }
-
-    void setHandoverState(@HandoverState int state) {
-        if (mHandoverState != state) {
-            String logStr = "State changed from " + handoverStateToString(mHandoverState)
-                    + " to " + handoverStateToString(state);
-            mHandoverLocalLog.log(logStr);
-            logd(logStr);
-            mHandoverState = state;
-        }
-    }
-
-    /** Sets the {@link DataCallSessionStats} mock for this data connection during unit testing. */
-    @VisibleForTesting
-    public void setDataCallSessionStats(DataCallSessionStats dataCallSessionStats) {
-        mDataCallSessionStats = dataCallSessionStats;
-    }
-
-    /**
-     * @return the string for msg.what as our info.
-     */
-    @Override
-    protected String getWhatToString(int what) {
-        return cmdToString(what);
-    }
-
-    private static String msgToString(Message msg) {
-        String retVal;
-        if (msg == null) {
-            retVal = "null";
-        } else {
-            StringBuilder   b = new StringBuilder();
-
-            b.append("{what=");
-            b.append(cmdToString(msg.what));
-
-            b.append(" when=");
-            TimeUtils.formatDuration(msg.getWhen() - SystemClock.uptimeMillis(), b);
-
-            if (msg.arg1 != 0) {
-                b.append(" arg1=");
-                b.append(msg.arg1);
-            }
-
-            if (msg.arg2 != 0) {
-                b.append(" arg2=");
-                b.append(msg.arg2);
-            }
-
-            if (msg.obj != null) {
-                b.append(" obj=");
-                b.append(msg.obj);
-            }
-
-            b.append(" target=");
-            b.append(msg.getTarget());
-
-            b.append(" replyTo=");
-            b.append(msg.replyTo);
-
-            b.append("}");
-
-            retVal = b.toString();
-        }
-        return retVal;
-    }
-
-    static void slog(String s) {
-        Rlog.d("DC", s);
-    }
-
-    /**
-     * Log with debug
-     *
-     * @param s is string log
-     */
-    @Override
-    protected void log(String s) {
-        Rlog.d(getName(), s);
-    }
-
-    /**
-     * Log with debug attribute
-     *
-     * @param s is string log
-     */
-    @Override
-    protected void logd(String s) {
-        Rlog.d(getName(), s);
-    }
-
-    /**
-     * Log with verbose attribute
-     *
-     * @param s is string log
-     */
-    @Override
-    protected void logv(String s) {
-        Rlog.v(getName(), s);
-    }
-
-    /**
-     * Log with info attribute
-     *
-     * @param s is string log
-     */
-    @Override
-    protected void logi(String s) {
-        Rlog.i(getName(), s);
-    }
-
-    /**
-     * Log with warning attribute
-     *
-     * @param s is string log
-     */
-    @Override
-    protected void logw(String s) {
-        Rlog.w(getName(), s);
-    }
-
-    /**
-     * Log with error attribute
-     *
-     * @param s is string log
-     */
-    @Override
-    protected void loge(String s) {
-        Rlog.e(getName(), s);
-    }
-
-    /**
-     * Log with error attribute
-     *
-     * @param s is string log
-     * @param e is a Throwable which logs additional information.
-     */
-    @Override
-    protected void loge(String s, Throwable e) {
-        Rlog.e(getName(), s, e);
-    }
-
-    /** Doesn't print mApnList of ApnContext's which would be recursive */
-    public synchronized String toStringSimple() {
-        return getName() + ": State=" + getCurrentState().getName()
-                + " mApnSetting=" + mApnSetting + " RefCount=" + mApnContexts.size()
-                + " mCid=" + mCid + " mCreateTime=" + mCreateTime
-                + " mLastastFailTime=" + mLastFailTime
-                + " mLastFailCause=" + DataFailCause.toString(mLastFailCause)
-                + " mTag=" + mTag
-                + " mLinkProperties=" + mLinkProperties
-                + " linkCapabilities=" + getNetworkCapabilities()
-                + " mRestrictedNetworkOverride=" + mRestrictedNetworkOverride;
-    }
-
-    @Override
-    public String toString() {
-        return "{" + toStringSimple() + " mApnContexts=" + mApnContexts + "}";
-    }
-
-    /** Check if the device is connected to NR 5G Non-Standalone network. */
-    private boolean isNRConnected() {
-        return mPhone.getServiceState().getNrState()
-                == NetworkRegistrationInfo.NR_STATE_CONNECTED;
-    }
-
-    /**
-     * @return The disallowed APN types bitmask
-     */
-    private @ApnType int getDisallowedApnTypes() {
-        CarrierConfigManager configManager = (CarrierConfigManager)
-                mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        int apnTypesBitmask = 0;
-        if (configManager != null) {
-            PersistableBundle bundle = configManager.getConfigForSubId(mSubId);
-            if (bundle != null) {
-                String key = (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                        ? CarrierConfigManager.KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY
-                        : CarrierConfigManager.KEY_CARRIER_WLAN_DISALLOWED_APN_TYPES_STRING_ARRAY;
-                if (bundle.getStringArray(key) != null) {
-                    String disallowedApnTypesString =
-                            TextUtils.join(",", bundle.getStringArray(key));
-                    if (!TextUtils.isEmpty(disallowedApnTypesString)) {
-                        apnTypesBitmask = ApnSetting.getApnTypesBitmaskFromString(
-                                disallowedApnTypesString);
-                    }
-                }
-            }
-        }
-
-        return apnTypesBitmask;
-    }
-
-    private void dumpToLog() {
-        dump(null, new PrintWriter(new StringWriter(0)) {
-            @Override
-            public void println(String s) {
-                DataConnection.this.logd(s);
-            }
-
-            @Override
-            public void flush() {
-            }
-        }, null);
-    }
-
-    /**
-     *  Re-calculate score and update through network agent if it changes.
-     */
-    private void updateScore() {
-        int oldScore = mScore;
-        mScore = calculateScore();
-        if (oldScore != mScore && mNetworkAgent != null) {
-            log("Updating score from " + oldScore + " to " + mScore);
-            mNetworkAgent.sendNetworkScore(mScore, this);
-        }
-    }
-
-    private int calculateScore() {
-        int score = OTHER_CONNECTION_SCORE;
-
-        // If it's serving a network request that asks NET_CAPABILITY_INTERNET and doesn't have
-        // specify a subId, this dataConnection is considered to be default Internet data
-        // connection. In this case we assign a slightly higher score of 50. The intention is
-        // it will not be replaced by other data connections accidentally in DSDS usecase.
-        for (ApnContext apnContext : mApnContexts.keySet()) {
-            for (NetworkRequest networkRequest : apnContext.getNetworkRequests()) {
-                if (networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                        && networkRequest.getNetworkSpecifier() == null) {
-                    score = DEFAULT_INTERNET_CONNECTION_SCORE;
-                    break;
-                }
-            }
-        }
-
-        return score;
-    }
-
-    private String handoverStateToString(@HandoverState int state) {
-        switch (state) {
-            case HANDOVER_STATE_IDLE: return "IDLE";
-            case HANDOVER_STATE_BEING_TRANSFERRED: return "BEING_TRANSFERRED";
-            case HANDOVER_STATE_COMPLETED: return "COMPLETED";
-            default: return "UNKNOWN";
-        }
-    }
-
-    private @DataState int getState() {
-        if (isInactive()) {
-            return TelephonyManager.DATA_DISCONNECTED;
-        } else if (isActivating()) {
-            return TelephonyManager.DATA_CONNECTING;
-        } else if (isActive()) {
-            // The data connection can only be suspended when it's in active state.
-            if (mIsSuspended) {
-                return TelephonyManager.DATA_SUSPENDED;
-            }
-            return TelephonyManager.DATA_CONNECTED;
-        } else if (isDisconnecting()) {
-            return TelephonyManager.DATA_DISCONNECTING;
-        }
-
-        return TelephonyManager.DATA_UNKNOWN;
-    }
-
-    /**
-     * Get precise data connection state
-     *
-     * @return The {@link PreciseDataConnectionState}
-     */
-    public PreciseDataConnectionState getPreciseDataConnectionState() {
-        return new PreciseDataConnectionState.Builder()
-                .setTransportType(mTransportType)
-                .setId(mCid)
-                .setState(getState())
-                .setApnSetting(mApnSetting)
-                .setLinkProperties(mLinkProperties)
-                .setNetworkType(getNetworkType())
-                .setFailCause(mDcFailCause)
-                .build();
-    }
-
-    /**
-     * Dump the current state.
-     *
-     * @param fd
-     * @param printWriter
-     * @param args
-     */
-    @Override
-    public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
-        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
-        pw.print("DataConnection ");
-        super.dump(fd, pw, args);
-        pw.flush();
-        pw.increaseIndent();
-        pw.println("transport type="
-                + AccessNetworkConstants.transportTypeToString(mTransportType));
-        pw.println("mApnContexts.size=" + mApnContexts.size());
-        pw.println("mApnContexts=" + mApnContexts);
-        pw.println("mApnSetting=" + mApnSetting);
-        pw.println("mTag=" + mTag);
-        pw.println("mCid=" + mCid);
-        pw.println("mConnectionParams=" + mConnectionParams);
-        pw.println("mDisconnectParams=" + mDisconnectParams);
-        pw.println("mDcFailCause=" + DataFailCause.toString(mDcFailCause));
-        pw.println("mPhone=" + mPhone);
-        pw.println("mSubId=" + mSubId);
-        pw.println("mLinkProperties=" + mLinkProperties);
-        pw.flush();
-        pw.println("mDataRegState=" + mDataRegState);
-        pw.println("mHandoverState=" + handoverStateToString(mHandoverState));
-        pw.println("mRilRat=" + mRilRat);
-        pw.println("mNetworkCapabilities=" + getNetworkCapabilities());
-        pw.println("mCreateTime=" + TimeUtils.logTimeOfDay(mCreateTime));
-        pw.println("mLastFailTime=" + TimeUtils.logTimeOfDay(mLastFailTime));
-        pw.println("mLastFailCause=" + DataFailCause.toString(mLastFailCause));
-        pw.println("mUserData=" + mUserData);
-        pw.println("mRestrictedNetworkOverride=" + mRestrictedNetworkOverride);
-        pw.println("mUnmeteredUseOnly=" + mUnmeteredUseOnly);
-        pw.println("mMmsUseOnly=" + mMmsUseOnly);
-        pw.println("mEnterpriseUse=" + mEnterpriseUse);
-        pw.println("mUnmeteredOverride=" + mUnmeteredOverride);
-        pw.println("mCongestedOverride=" + mCongestedOverride);
-        pw.println("mDownlinkBandwidth" + mDownlinkBandwidth);
-        pw.println("mUplinkBandwidth=" + mUplinkBandwidth);
-        pw.println("mDefaultQos=" + mDefaultQos);
-        pw.println("mQosBearerSessions=" + mQosBearerSessions);
-        pw.println("disallowedApnTypes="
-                + ApnSetting.getApnTypesStringFromBitmask(getDisallowedApnTypes()));
-        pw.println("mInstanceNumber=" + mInstanceNumber);
-        pw.println("mAc=" + mAc);
-        pw.println("mScore=" + mScore);
-        if (mNetworkAgent != null) {
-            mNetworkAgent.dump(fd, pw, args);
-        }
-        pw.println("handover local log:");
-        pw.increaseIndent();
-        mHandoverLocalLog.dump(fd, pw, args);
-        pw.decreaseIndent();
-        pw.decreaseIndent();
-        pw.println();
-        pw.flush();
-    }
-
-    /**
-     * Class used to track VCN-defined Network policies for this DataConnection.
-     *
-     * <p>MUST be registered with the associated DataConnection's Handler.
-     */
-    private class DataConnectionVcnNetworkPolicyChangeListener
-            implements VcnNetworkPolicyChangeListener {
-        @Override
-        public void onPolicyChanged() {
-            // Poll the current underlying Network policy from VcnManager and send to NetworkAgent.
-            final NetworkCapabilities networkCapabilities = getNetworkCapabilities();
-            VcnNetworkPolicyResult policyResult =
-                    mVcnManager.applyVcnNetworkPolicy(
-                            networkCapabilities, getLinkProperties());
-            if (policyResult.isTeardownRequested()) {
-                tearDownAll(
-                        Phone.REASON_VCN_REQUESTED_TEARDOWN,
-                        DcTracker.RELEASE_TYPE_DETACH,
-                        null /* onCompletedMsg */);
-            }
-
-            if (mNetworkAgent != null) {
-                mNetworkAgent.sendNetworkCapabilities(networkCapabilities, DataConnection.this);
-            }
-        }
-    }
-}
-
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataConnectionReasons.java b/src/java/com/android/internal/telephony/dataconnection/DataConnectionReasons.java
deleted file mode 100644
index 68f2ab3..0000000
--- a/src/java/com/android/internal/telephony/dataconnection/DataConnectionReasons.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2017 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.dataconnection;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.util.HashSet;
-
-/**
- * The class to describe the reasons of allowing or disallowing to establish a data connection.
- */
-public class DataConnectionReasons {
-    private HashSet<DataDisallowedReasonType> mDataDisallowedReasonSet = new HashSet<>();
-    private DataAllowedReasonType mDataAllowedReason = DataAllowedReasonType.NONE;
-
-    public DataConnectionReasons() {}
-
-    void add(DataDisallowedReasonType reason) {
-        // Adding a disallowed reason will clean up the allowed reason because they are
-        // mutual exclusive.
-        mDataAllowedReason = DataAllowedReasonType.NONE;
-        mDataDisallowedReasonSet.add(reason);
-    }
-
-    void add(DataAllowedReasonType reason) {
-        // Adding an allowed reason will clean up the disallowed reasons because they are
-        // mutual exclusive.
-        mDataDisallowedReasonSet.clear();
-
-        // Only higher priority allowed reason can overwrite the old one. See
-        // DataAllowedReasonType for the oder.
-        if (reason.ordinal() > mDataAllowedReason.ordinal()) {
-            mDataAllowedReason = reason;
-        }
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder reasonStr = new StringBuilder();
-        if (mDataDisallowedReasonSet.size() > 0) {
-            reasonStr.append("Data disallowed reasons:");
-            for (DataDisallowedReasonType reason : mDataDisallowedReasonSet) {
-                reasonStr.append(" ").append(reason);
-            }
-        } else {
-            reasonStr.append("Data allowed reason:");
-            reasonStr.append(" ").append(mDataAllowedReason);
-        }
-        return reasonStr.toString();
-    }
-
-    void copyFrom(DataConnectionReasons reasons) {
-        this.mDataDisallowedReasonSet = reasons.mDataDisallowedReasonSet;
-        this.mDataAllowedReason = reasons.mDataAllowedReason;
-    }
-
-    boolean allowed() {
-        return mDataDisallowedReasonSet.size() == 0;
-    }
-
-    /**
-     * Check if it contains a certain disallowed reason.
-     *
-     * @param reason The disallowed reason to check.
-     * @return {@code true} if the provided reason matches one of the disallowed reasons.
-     */
-    @VisibleForTesting
-    public boolean contains(DataDisallowedReasonType reason) {
-        return mDataDisallowedReasonSet.contains(reason);
-    }
-
-    /**
-     * Check if only one disallowed reason prevent data connection.
-     *
-     * @param reason The given reason to check
-     * @return True if the given reason is the only one that prevents data connection
-     */
-    public boolean containsOnly(DataDisallowedReasonType reason) {
-        return mDataDisallowedReasonSet.size() == 1 && contains(reason);
-    }
-
-    boolean contains(DataAllowedReasonType reason) {
-        return reason == mDataAllowedReason;
-    }
-
-    boolean containsHardDisallowedReasons() {
-        for (DataDisallowedReasonType reason : mDataDisallowedReasonSet) {
-            if (reason.isHardReason()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    // Disallowed reasons. There could be multiple reasons if data connection is not allowed.
-    public enum DataDisallowedReasonType {
-        // Soft failure reasons. Normally the reasons from users or policy settings.
-
-        // Data is disabled by the user or policy.
-        DATA_DISABLED(false),
-        // Data roaming is disabled by the user.
-        ROAMING_DISABLED(false),
-        // Default data not selected.
-        DEFAULT_DATA_UNSELECTED(false),
-
-        // Belows are all hard failure reasons.
-        NOT_ATTACHED(true),
-        SIM_NOT_READY(true),
-        INVALID_PHONE_STATE(true),
-        CONCURRENT_VOICE_DATA_NOT_ALLOWED(true),
-        PS_RESTRICTED(true),
-        UNDESIRED_POWER_STATE(true),
-        INTERNAL_DATA_DISABLED(true),
-        RADIO_DISABLED_BY_CARRIER(true),
-        // Not in the right state for data call setup.
-        APN_NOT_CONNECTABLE(true),
-        // Data is in connecting state. No need to send another setup request.
-        DATA_IS_CONNECTING(true),
-        // Data is being disconnected. Telephony will retry after disconnected.
-        DATA_IS_DISCONNECTING(true),
-        // Data is already connected. No need to setup data again.
-        DATA_ALREADY_CONNECTED(true),
-        // certain APNs are not allowed on IWLAN in legacy mode.
-        ON_IWLAN(true),
-        // certain APNs are only allowed when the device is camped on NR.
-        NOT_ON_NR(true),
-        // Data is not allowed while device is in emergency callback mode.
-        IN_ECBM(true),
-        // The given APN type's preferred transport has switched.
-        ON_OTHER_TRANSPORT(true),
-        // Underlying data service is not bound.
-        DATA_SERVICE_NOT_READY(true),
-        // Qualified networks service does not allow certain types of APN brought up on either
-        // cellular or IWLAN.
-        DISABLED_BY_QNS(true),
-        // Data is throttled. The network explicitly requested device not to establish data
-        // connection for a certain period.
-        DATA_THROTTLED(true);
-
-        private boolean mIsHardReason;
-
-        boolean isHardReason() {
-            return mIsHardReason;
-        }
-
-        DataDisallowedReasonType(boolean isHardReason) {
-            mIsHardReason = isHardReason;
-        }
-    }
-
-    // Data allowed reasons. There will be only one reason if data is allowed.
-    enum DataAllowedReasonType {
-        // Note that unlike disallowed reasons, we only have one allowed reason every time
-        // when we check data is allowed or not. The order of these allowed reasons is very
-        // important. The lower ones take precedence over the upper ones.
-        NONE,
-        NORMAL,
-        UNMETERED_APN,
-        RESTRICTED_REQUEST,
-        EMERGENCY_APN,
-    }
-}
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataEnabledSettings.java b/src/java/com/android/internal/telephony/dataconnection/DataEnabledSettings.java
deleted file mode 100644
index 305b4a8..0000000
--- a/src/java/com/android/internal/telephony/dataconnection/DataEnabledSettings.java
+++ /dev/null
@@ -1,566 +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.dataconnection;
-
-
-import static android.telephony.PhoneStateListener.LISTEN_CALL_STATE;
-import static android.telephony.PhoneStateListener.LISTEN_NONE;
-
-import android.annotation.IntDef;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.os.Handler;
-import android.os.RegistrantList;
-import android.os.SystemProperties;
-import android.provider.Settings;
-import android.sysprop.TelephonyProperties;
-import android.telephony.Annotation.CallState;
-import android.telephony.CarrierConfigManager;
-import android.telephony.PhoneStateListener;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.telephony.data.ApnSetting;
-import android.util.LocalLog;
-import android.util.Pair;
-
-import com.android.internal.telephony.GlobalSettingsHelper;
-import com.android.internal.telephony.MultiSimSettingController;
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.SubscriptionController;
-import com.android.internal.telephony.data.DataEnabledOverride;
-import com.android.telephony.Rlog;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * The class to hold different data enabled/disabled settings. Also it allows clients to register
- * for overall data enabled setting changed event.
- * @hide
- */
-public class DataEnabledSettings {
-
-    private static final String LOG_TAG = "DataEnabledSettings";
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = {"REASON_"},
-            value = {
-                    REASON_REGISTERED,
-                    REASON_INTERNAL_DATA_ENABLED,
-                    REASON_USER_DATA_ENABLED,
-                    REASON_POLICY_DATA_ENABLED,
-                    REASON_DATA_ENABLED_BY_CARRIER,
-                    REASON_PROVISIONED_CHANGED,
-                    REASON_PROVISIONING_DATA_ENABLED_CHANGED,
-                    REASON_OVERRIDE_RULE_CHANGED,
-                    REASON_OVERRIDE_CONDITION_CHANGED,
-                    REASON_THERMAL_DATA_ENABLED
-            })
-    public @interface DataEnabledChangedReason {}
-
-    public static final int REASON_REGISTERED = 0;
-
-    public static final int REASON_INTERNAL_DATA_ENABLED = 1;
-
-    public static final int REASON_USER_DATA_ENABLED = 2;
-
-    public static final int REASON_POLICY_DATA_ENABLED = 3;
-
-    public static final int REASON_DATA_ENABLED_BY_CARRIER = 4;
-
-    public static final int REASON_PROVISIONED_CHANGED = 5;
-
-    public static final int REASON_PROVISIONING_DATA_ENABLED_CHANGED = 6;
-
-    public static final int REASON_OVERRIDE_RULE_CHANGED = 7;
-
-    public static final int REASON_OVERRIDE_CONDITION_CHANGED = 8;
-
-    public static final int REASON_THERMAL_DATA_ENABLED = 9;
-
-    /**
-     * responds to the setInternalDataEnabled call - used internally to turn off data.
-     * For example during emergency calls
-     */
-    private boolean mInternalDataEnabled = true;
-
-    /**
-     * Flag indicating data allowed by network policy manager or not.
-     */
-    private boolean mPolicyDataEnabled = true;
-
-    /**
-     * Indicate if metered APNs are enabled by the carrier. set false to block all the metered APNs
-     * from continuously sending requests, which causes undesired network load.
-     */
-    private boolean mCarrierDataEnabled = true;
-
-    /**
-     * Flag indicating data allowed by Thermal service or not.
-     */
-    private boolean mThermalDataEnabled = true;
-
-    /**
-     * Flag indicating whether data is allowed or not for the device. It can be disabled by
-     * user, carrier, policy or thermal
-     */
-    private boolean mIsDataEnabled = false;
-
-    private final Phone mPhone;
-
-    private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-
-    private ContentResolver mResolver = null;
-
-    private final RegistrantList mOverallDataEnabledChangedRegistrants = new RegistrantList();
-
-    // TODO: Merge this with mOverallDataEnabledChangedRegistrants. In the future, notifying data
-    // enabled changed with APN types bitmask
-    private final RegistrantList mOverallDataEnabledOverrideChangedRegistrants =
-            new RegistrantList();
-
-    private final LocalLog mSettingChangeLocalLog = new LocalLog(32);
-
-    private DataEnabledOverride mDataEnabledOverride;
-
-    private TelephonyManager mTelephonyManager;
-
-    // for msim, user data enabled setting depends on subId.
-    private final SubscriptionManager.OnSubscriptionsChangedListener
-            mOnSubscriptionsChangeListener =
-            new SubscriptionManager.OnSubscriptionsChangedListener() {
-                @Override
-                public void onSubscriptionsChanged() {
-                    synchronized (this) {
-                        if (mSubId != mPhone.getSubId()) {
-                            log("onSubscriptionsChanged subId: " + mSubId + " to: "
-                                    + mPhone.getSubId());
-                            mSubId = mPhone.getSubId();
-                            mDataEnabledOverride = getDataEnabledOverride();
-                            updatePhoneStateListener();
-                            updateDataEnabledAndNotify(REASON_USER_DATA_ENABLED);
-                            mPhone.notifyUserMobileDataStateChanged(isUserDataEnabled());
-                        }
-                    }
-                }
-            };
-
-    private void updatePhoneStateListener() {
-        mTelephonyManager.listen(mPhoneStateListener, LISTEN_NONE);
-        if (SubscriptionManager.isUsableSubscriptionId(mSubId)) {
-            mTelephonyManager = mTelephonyManager.createForSubscriptionId(mSubId);
-        }
-        mTelephonyManager.listen(mPhoneStateListener, LISTEN_CALL_STATE);
-    }
-
-    private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
-        @Override
-        public void onCallStateChanged(@CallState int state, String phoneNumber) {
-            updateDataEnabledAndNotify(REASON_OVERRIDE_CONDITION_CHANGED);
-        }
-    };
-
-    @Override
-    public String toString() {
-        return "[mInternalDataEnabled=" + mInternalDataEnabled
-                + ", isUserDataEnabled=" + isUserDataEnabled()
-                + ", isProvisioningDataEnabled=" + isProvisioningDataEnabled()
-                + ", mPolicyDataEnabled=" + mPolicyDataEnabled
-                + ", mCarrierDataEnabled=" + mCarrierDataEnabled
-                + ", mIsDataEnabled=" + mIsDataEnabled
-                + ", mThermalDataEnabled=" + mThermalDataEnabled
-                + ", " + mDataEnabledOverride
-                + "]";
-    }
-
-    public DataEnabledSettings(Phone phone) {
-        mPhone = phone;
-        mResolver = mPhone.getContext().getContentResolver();
-        SubscriptionManager subscriptionManager = (SubscriptionManager) mPhone.getContext()
-                .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
-        subscriptionManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangeListener);
-        mTelephonyManager = (TelephonyManager) mPhone.getContext()
-                .getSystemService(Context.TELEPHONY_SERVICE);
-        mDataEnabledOverride = getDataEnabledOverride();
-        updateDataEnabled();
-    }
-
-    private DataEnabledOverride getDataEnabledOverride() {
-        return new DataEnabledOverride(SubscriptionController.getInstance()
-                .getDataEnabledOverrideRules(mPhone.getSubId()));
-    }
-
-    public synchronized void setInternalDataEnabled(boolean enabled) {
-        if (mInternalDataEnabled != enabled) {
-            localLog("InternalDataEnabled", enabled);
-            mInternalDataEnabled = enabled;
-            updateDataEnabledAndNotify(REASON_INTERNAL_DATA_ENABLED);
-        }
-    }
-    public synchronized boolean isInternalDataEnabled() {
-        return mInternalDataEnabled;
-    }
-
-    private synchronized void setUserDataEnabled(boolean enabled) {
-        // By default the change should propagate to the group.
-        setUserDataEnabled(enabled, true);
-    }
-
-    /**
-     * @param notifyMultiSimSettingController if setUserDataEnabled is already from propagating
-     *        from MultiSimSettingController, don't notify MultiSimSettingController again.
-     *        For example, if sub1 and sub2 are in the same group and user enables data for sub
-     *        1, sub 2 will also be enabled but with propagateToGroup = false.
-     */
-    public synchronized void setUserDataEnabled(boolean enabled,
-            boolean notifyMultiSimSettingController) {
-        // Can't disable data for stand alone opportunistic subscription.
-        if (isStandAloneOpportunistic(mPhone.getSubId(), mPhone.getContext()) && !enabled) return;
-
-        boolean changed = GlobalSettingsHelper.setInt(mPhone.getContext(),
-                Settings.Global.MOBILE_DATA, mPhone.getSubId(), (enabled ? 1 : 0));
-        if (changed) {
-            localLog("UserDataEnabled", enabled);
-            mPhone.notifyUserMobileDataStateChanged(enabled);
-            updateDataEnabledAndNotify(REASON_USER_DATA_ENABLED);
-            if (notifyMultiSimSettingController) {
-                MultiSimSettingController.getInstance().notifyUserDataEnabled(
-                        mPhone.getSubId(), enabled);
-            }
-        }
-    }
-
-    /**
-     * Policy control of data connection with reason
-     * @param reason the reason the data enable change is taking place
-     * @param enabled True if enabling the data, otherwise disabling.
-     */
-    public synchronized void setDataEnabled(@TelephonyManager.DataEnabledReason int reason,
-            boolean enabled) {
-        switch (reason) {
-            case TelephonyManager.DATA_ENABLED_REASON_USER:
-                setUserDataEnabled(enabled);
-                break;
-            case TelephonyManager.DATA_ENABLED_REASON_CARRIER:
-                setCarrierDataEnabled(enabled);
-                break;
-            case TelephonyManager.DATA_ENABLED_REASON_POLICY:
-                setPolicyDataEnabled(enabled);
-                break;
-            case TelephonyManager.DATA_ENABLED_REASON_THERMAL:
-                setThermalDataEnabled(enabled);
-                break;
-            default:
-                log("Invalid data enable reason " + reason);
-                break;
-        }
-    }
-
-    public synchronized boolean isUserDataEnabled() {
-        // User data should always be true for opportunistic subscription.
-        if (isStandAloneOpportunistic(mPhone.getSubId(), mPhone.getContext())) return true;
-
-        boolean defaultVal = TelephonyProperties.mobile_data().orElse(true);
-
-        return GlobalSettingsHelper.getBoolean(mPhone.getContext(),
-                Settings.Global.MOBILE_DATA, mPhone.getSubId(), defaultVal);
-    }
-
-    /**
-     * Set whether always allowing MMS data connection.
-     *
-     * @param alwaysAllow {@code true} if MMS data is always allowed.
-     *
-     * @return {@code false} if the setting is changed.
-     */
-    public synchronized boolean setAlwaysAllowMmsData(boolean alwaysAllow) {
-        localLog("setAlwaysAllowMmsData", alwaysAllow);
-        mDataEnabledOverride.setAlwaysAllowMms(alwaysAllow);
-        boolean changed = SubscriptionController.getInstance()
-                .setDataEnabledOverrideRules(mPhone.getSubId(), mDataEnabledOverride.getRules());
-        if (changed) {
-            updateDataEnabledAndNotify(REASON_OVERRIDE_RULE_CHANGED);
-            notifyDataEnabledOverrideChanged();
-        }
-
-        return changed;
-    }
-
-    /**
-     * Set allowing mobile data during voice call. This is used for allowing data on the non-default
-     * data SIM. When a voice call is placed on the non-default data SIM on DSDS devices, users will
-     * not be able to use mobile data. By calling this API, data will be temporarily enabled on the
-     * non-default data SIM during the life cycle of the voice call.
-     *
-     * @param allow {@code true} if allowing using data during voice call, {@code false} if
-     * disallowed
-     *
-     * @return {@code true} if operation is successful. otherwise {@code false}.
-     */
-    public synchronized boolean setAllowDataDuringVoiceCall(boolean allow) {
-        localLog("setAllowDataDuringVoiceCall", allow);
-        if (allow == isDataAllowedInVoiceCall()) {
-            return true;
-        }
-        mDataEnabledOverride.setDataAllowedInVoiceCall(allow);
-
-        boolean changed = SubscriptionController.getInstance()
-                .setDataEnabledOverrideRules(mPhone.getSubId(), mDataEnabledOverride.getRules());
-        if (changed) {
-            updateDataEnabledAndNotify(REASON_OVERRIDE_RULE_CHANGED);
-            notifyDataEnabledOverrideChanged();
-        }
-
-        return changed;
-    }
-
-    /**
-     * Check if data is allowed during voice call.
-     *
-     * @return {@code true} if data is allowed during voice call.
-     */
-    public synchronized boolean isDataAllowedInVoiceCall() {
-        return mDataEnabledOverride.isDataAllowedInVoiceCall();
-    }
-
-    public synchronized boolean isMmsAlwaysAllowed() {
-        return mDataEnabledOverride.isMmsAlwaysAllowed();
-    }
-
-    private synchronized void setPolicyDataEnabled(boolean enabled) {
-        if (mPolicyDataEnabled != enabled) {
-            localLog("PolicyDataEnabled", enabled);
-            mPolicyDataEnabled = enabled;
-            updateDataEnabledAndNotify(REASON_POLICY_DATA_ENABLED);
-        }
-    }
-
-    public synchronized boolean isPolicyDataEnabled() {
-        return mPolicyDataEnabled;
-    }
-
-    private synchronized void setCarrierDataEnabled(boolean enabled) {
-        if (mCarrierDataEnabled != enabled) {
-            localLog("CarrierDataEnabled", enabled);
-            mCarrierDataEnabled = enabled;
-            updateDataEnabledAndNotify(REASON_DATA_ENABLED_BY_CARRIER);
-        }
-    }
-
-    public synchronized boolean isCarrierDataEnabled() {
-        return mCarrierDataEnabled;
-    }
-
-    private synchronized void setThermalDataEnabled(boolean enabled) {
-        if (mThermalDataEnabled != enabled) {
-            localLog("ThermalDataEnabled", enabled);
-            mThermalDataEnabled = enabled;
-            updateDataEnabledAndNotify(REASON_THERMAL_DATA_ENABLED);
-        }
-    }
-
-    public synchronized boolean isThermalDataEnabled() {
-        return mThermalDataEnabled;
-    }
-
-    public synchronized void updateProvisionedChanged() {
-        updateDataEnabledAndNotify(REASON_PROVISIONED_CHANGED);
-    }
-
-    public synchronized void updateProvisioningDataEnabled() {
-        updateDataEnabledAndNotify(REASON_PROVISIONING_DATA_ENABLED_CHANGED);
-    }
-
-    public synchronized boolean isDataEnabled() {
-        return mIsDataEnabled;
-    }
-
-    /**
-     * Check if data is enabled for a specific reason {@@TelephonyManager.DataEnabledReason}
-     *
-     * @return {@code true} if the overall data is enabled; {@code false} if not.
-     */
-    public synchronized boolean isDataEnabledForReason(
-            @TelephonyManager.DataEnabledReason int reason) {
-        switch (reason) {
-            case TelephonyManager.DATA_ENABLED_REASON_USER:
-                return isUserDataEnabled();
-            case TelephonyManager.DATA_ENABLED_REASON_CARRIER:
-                return isCarrierDataEnabled();
-            case TelephonyManager.DATA_ENABLED_REASON_POLICY:
-                return isPolicyDataEnabled();
-            case TelephonyManager.DATA_ENABLED_REASON_THERMAL:
-                return isThermalDataEnabled();
-            default:
-                return false;
-        }
-    }
-
-    private synchronized void updateDataEnabledAndNotify(int reason) {
-        boolean prevDataEnabled = mIsDataEnabled;
-
-        updateDataEnabled();
-
-        if (prevDataEnabled != mIsDataEnabled) {
-            notifyDataEnabledChanged(!prevDataEnabled, reason);
-        }
-    }
-
-    private synchronized void updateDataEnabled() {
-        if (isProvisioning()) {
-            mIsDataEnabled = isProvisioningDataEnabled();
-        } else {
-            mIsDataEnabled = mInternalDataEnabled && (isUserDataEnabled() || mDataEnabledOverride
-                    .shouldOverrideDataEnabledSettings(mPhone, ApnSetting.TYPE_ALL))
-                    && mPolicyDataEnabled && mCarrierDataEnabled && mThermalDataEnabled;
-        }
-    }
-
-    public boolean isProvisioning() {
-        return Settings.Global.getInt(mResolver, Settings.Global.DEVICE_PROVISIONED, 0) == 0;
-    }
-    /**
-     * In provisioning, we might want to have enable mobile data during provisioning. It depends
-     * on value of Settings.Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED which is set by
-     * setupwizard. It only matters if it's in provisioning stage.
-     * @return whether we are enabling userData during provisioning stage.
-     */
-    public boolean isProvisioningDataEnabled() {
-        final String prov_property = SystemProperties.get("ro.com.android.prov_mobiledata",
-                "false");
-        boolean retVal = "true".equalsIgnoreCase(prov_property);
-
-        final int prov_mobile_data = Settings.Global.getInt(mResolver,
-                Settings.Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED,
-                retVal ? 1 : 0);
-        retVal = prov_mobile_data != 0;
-        log("getDataEnabled during provisioning retVal=" + retVal + " - (" + prov_property
-                + ", " + prov_mobile_data + ")");
-
-        return retVal;
-    }
-
-    public synchronized void setDataRoamingEnabled(boolean enabled) {
-        // will trigger handleDataOnRoamingChange() through observer
-        boolean changed = GlobalSettingsHelper.setBoolean(mPhone.getContext(),
-                Settings.Global.DATA_ROAMING, mPhone.getSubId(), enabled);
-
-        if (changed) {
-            localLog("setDataRoamingEnabled", enabled);
-            MultiSimSettingController.getInstance().notifyRoamingDataEnabled(mPhone.getSubId(),
-                    enabled);
-        }
-    }
-
-    /**
-     * Return current {@link android.provider.Settings.Global#DATA_ROAMING} value.
-     */
-    public synchronized boolean getDataRoamingEnabled() {
-        return GlobalSettingsHelper.getBoolean(mPhone.getContext(),
-                Settings.Global.DATA_ROAMING, mPhone.getSubId(), getDefaultDataRoamingEnabled());
-    }
-
-    /**
-     * get default values for {@link Settings.Global#DATA_ROAMING}
-     * return {@code true} if either
-     * {@link CarrierConfigManager#KEY_CARRIER_DEFAULT_DATA_ROAMING_ENABLED_BOOL} or
-     * system property ro.com.android.dataroaming is set to true. otherwise return {@code false}
-     */
-    public synchronized boolean getDefaultDataRoamingEnabled() {
-        final CarrierConfigManager configMgr = (CarrierConfigManager)
-                mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        boolean isDataRoamingEnabled = "true".equalsIgnoreCase(SystemProperties.get(
-                "ro.com.android.dataroaming", "false"));
-        isDataRoamingEnabled |= configMgr.getConfigForSubId(mPhone.getSubId()).getBoolean(
-                CarrierConfigManager.KEY_CARRIER_DEFAULT_DATA_ROAMING_ENABLED_BOOL);
-        return isDataRoamingEnabled;
-    }
-
-    private void notifyDataEnabledChanged(boolean enabled, int reason) {
-        mOverallDataEnabledChangedRegistrants.notifyResult(new Pair<>(enabled, reason));
-        mPhone.notifyDataEnabled(enabled, reason);
-    }
-
-    public void registerForDataEnabledChanged(Handler h, int what, Object obj) {
-        mOverallDataEnabledChangedRegistrants.addUnique(h, what, obj);
-        notifyDataEnabledChanged(isDataEnabled(), REASON_REGISTERED);
-    }
-
-    public void unregisterForDataEnabledChanged(Handler h) {
-        mOverallDataEnabledChangedRegistrants.remove(h);
-    }
-
-    private void notifyDataEnabledOverrideChanged() {
-        mOverallDataEnabledOverrideChangedRegistrants.notifyRegistrants();
-    }
-
-    /**
-     * Register for data enabled override changed event.
-     *
-     * @param h The handler
-     * @param what The event
-     */
-    public void registerForDataEnabledOverrideChanged(Handler h, int what) {
-        mOverallDataEnabledOverrideChangedRegistrants.addUnique(h, what, null);
-        notifyDataEnabledOverrideChanged();
-    }
-
-    /**
-     * Unregistered for data enabled override changed event.
-     *
-     * @param h The handler
-     */
-    public void unregisterForDataEnabledOverrideChanged(Handler h) {
-        mOverallDataEnabledOverrideChangedRegistrants.remove(h);
-    }
-
-    private static boolean isStandAloneOpportunistic(int subId, Context context) {
-        SubscriptionInfo info = SubscriptionController.getInstance().getActiveSubscriptionInfo(
-                subId, context.getOpPackageName(), context.getAttributionTag());
-        return (info != null) && info.isOpportunistic() && info.getGroupUuid() == null;
-    }
-
-    public synchronized boolean isDataEnabled(int apnType) {
-        if (isProvisioning()) {
-            return isProvisioningDataEnabled();
-        } else {
-            boolean userDataEnabled = isUserDataEnabled();
-            // Check if we should temporarily enable data in certain conditions.
-            boolean isDataEnabledOverridden = mDataEnabledOverride
-                    .shouldOverrideDataEnabledSettings(mPhone, apnType);
-
-            return (mInternalDataEnabled && mPolicyDataEnabled && mCarrierDataEnabled
-                    && mThermalDataEnabled && (userDataEnabled || isDataEnabledOverridden));
-        }
-    }
-
-    private void log(String s) {
-        Rlog.d(LOG_TAG, "[" + mPhone.getPhoneId() + "]" + s);
-    }
-
-    private void localLog(String name, boolean value) {
-        mSettingChangeLocalLog.log(name + " change to " + value);
-    }
-
-    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println(" DataEnabledSettings=");
-        mSettingChangeLocalLog.dump(fd, pw, args);
-    }
-}
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataServiceManager.java b/src/java/com/android/internal/telephony/dataconnection/DataServiceManager.java
deleted file mode 100644
index d006004..0000000
--- a/src/java/com/android/internal/telephony/dataconnection/DataServiceManager.java
+++ /dev/null
@@ -1,978 +0,0 @@
-/*
- * Copyright 2018 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.dataconnection;
-
-import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
-import static android.text.format.DateUtils.SECOND_IN_MILLIS;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.AppOpsManager;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.net.LinkProperties;
-import android.os.AsyncResult;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.PersistableBundle;
-import android.os.RegistrantList;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.permission.LegacyPermissionManager;
-import android.telephony.AccessNetworkConstants;
-import android.telephony.AccessNetworkConstants.TransportType;
-import android.telephony.AnomalyReporter;
-import android.telephony.CarrierConfigManager;
-import android.telephony.SubscriptionManager;
-import android.telephony.data.DataCallResponse;
-import android.telephony.data.DataProfile;
-import android.telephony.data.DataService;
-import android.telephony.data.DataServiceCallback;
-import android.telephony.data.IDataService;
-import android.telephony.data.IDataServiceCallback;
-import android.telephony.data.NetworkSliceInfo;
-import android.telephony.data.TrafficDescriptor;
-import android.text.TextUtils;
-
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.PhoneConfigurationManager;
-import com.android.internal.telephony.util.TelephonyUtils;
-import com.android.telephony.Rlog;
-
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CountDownLatch;
-
-/**
- * Data service manager manages handling data requests and responses on data services (e.g.
- * Cellular data service, IWLAN data service).
- */
-public class DataServiceManager extends Handler {
-    private static final boolean DBG = true;
-
-    static final String DATA_CALL_RESPONSE = "data_call_response";
-
-    private static final int EVENT_BIND_DATA_SERVICE = 1;
-
-    private static final int EVENT_WATCHDOG_TIMEOUT = 2;
-
-    private static final long REQUEST_UNRESPONDED_TIMEOUT = 10 * MINUTE_IN_MILLIS; // 10 mins
-
-    private static final long CHANGE_PERMISSION_TIMEOUT_MS = 15 * SECOND_IN_MILLIS; // 15 secs
-
-    private final Phone mPhone;
-
-    private final String mTag;
-
-    private final CarrierConfigManager mCarrierConfigManager;
-    private final AppOpsManager mAppOps;
-    private final LegacyPermissionManager mPermissionManager;
-
-    private final int mTransportType;
-
-    private boolean mBound;
-
-    private IDataService mIDataService;
-
-    private DataServiceManagerDeathRecipient mDeathRecipient;
-
-    private final RegistrantList mServiceBindingChangedRegistrants = new RegistrantList();
-
-    private final Map<IBinder, Message> mMessageMap = new ConcurrentHashMap<>();
-
-    private final RegistrantList mDataCallListChangedRegistrants = new RegistrantList();
-
-    private final RegistrantList mApnUnthrottledRegistrants = new RegistrantList();
-
-    private String mTargetBindingPackageName;
-
-    private CellularDataServiceConnection mServiceConnection;
-
-    private final UUID mAnomalyUUID = UUID.fromString("fc1956de-c080-45de-8431-a1faab687110");
-    private String mLastBoundPackageName;
-
-    /**
-     * Helpful for logging
-     * @return the tag name
-     *
-     * @hide
-     */
-    public String getTag() {
-        return mTag;
-    }
-
-    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(action)
-                    && mPhone.getPhoneId() == intent.getIntExtra(
-                    CarrierConfigManager.EXTRA_SLOT_INDEX, 0)) {
-                // We should wait for carrier config changed event because the target binding
-                // package name can come from the carrier config. Note that we still get this event
-                // even when SIM is absent.
-                if (DBG) log("Carrier config changed. Try to bind data service.");
-                sendEmptyMessage(EVENT_BIND_DATA_SERVICE);
-            }
-        }
-    };
-
-    private class DataServiceManagerDeathRecipient implements IBinder.DeathRecipient {
-        @Override
-        public void binderDied() {
-            // TODO: try to rebind the service.
-            String message = "Data service " + mLastBoundPackageName +  " for transport type "
-                    + AccessNetworkConstants.transportTypeToString(mTransportType) + " died.";
-            loge(message);
-            AnomalyReporter.reportAnomaly(mAnomalyUUID, message, mPhone.getCarrierId());
-        }
-    }
-
-    private void grantPermissionsToService(String packageName) {
-        final String[] pkgToGrant = {packageName};
-        CountDownLatch latch = new CountDownLatch(1);
-        try {
-            mPermissionManager.grantDefaultPermissionsToEnabledTelephonyDataServices(
-                    pkgToGrant, UserHandle.of(UserHandle.myUserId()), Runnable::run,
-                    isSuccess -> {
-                        if (isSuccess) {
-                            latch.countDown();
-                        } else {
-                            loge("Failed to grant permissions to service.");
-                        }
-                    });
-            TelephonyUtils.waitUntilReady(latch, CHANGE_PERMISSION_TIMEOUT_MS);
-            mAppOps.setMode(AppOpsManager.OPSTR_MANAGE_IPSEC_TUNNELS,
-                    UserHandle.myUserId(), pkgToGrant[0], AppOpsManager.MODE_ALLOWED);
-            mAppOps.setMode(AppOpsManager.OPSTR_FINE_LOCATION,
-                    UserHandle.myUserId(), pkgToGrant[0], AppOpsManager.MODE_ALLOWED);
-        } catch (RuntimeException e) {
-            loge("Binder to package manager died, permission grant for DataService failed.");
-            throw e;
-        }
-    }
-
-    /**
-     * Loop through all DataServices installed on the system and revoke permissions from any that
-     * are not currently the WWAN or WLAN data service.
-     */
-    private void revokePermissionsFromUnusedDataServices() {
-        // Except the current data services from having their permissions removed.
-        Set<String> dataServices = getAllDataServicePackageNames();
-        for (int transportType : mPhone.getAccessNetworksManager().getAvailableTransports()) {
-            dataServices.remove(getDataServicePackageName(transportType));
-        }
-
-        CountDownLatch latch = new CountDownLatch(1);
-        try {
-            String[] dataServicesArray = new String[dataServices.size()];
-            dataServices.toArray(dataServicesArray);
-            mPermissionManager.revokeDefaultPermissionsFromDisabledTelephonyDataServices(
-                    dataServicesArray, UserHandle.of(UserHandle.myUserId()), Runnable::run,
-                    isSuccess -> {
-                        if (isSuccess) {
-                            latch.countDown();
-                        } else {
-                            loge("Failed to revoke permissions from data services.");
-                        }
-                    });
-            TelephonyUtils.waitUntilReady(latch, CHANGE_PERMISSION_TIMEOUT_MS);
-            for (String pkg : dataServices) {
-                mAppOps.setMode(AppOpsManager.OPSTR_MANAGE_IPSEC_TUNNELS, UserHandle.myUserId(),
-                        pkg, AppOpsManager.MODE_ERRORED);
-                mAppOps.setMode(AppOpsManager.OPSTR_FINE_LOCATION, UserHandle.myUserId(),
-                        pkg, AppOpsManager.MODE_ERRORED);
-            }
-        } catch (RuntimeException e) {
-            loge("Binder to package manager died; failed to revoke DataService permissions.");
-            throw e;
-        }
-    }
-
-    private final class CellularDataServiceConnection implements ServiceConnection {
-        @Override
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            if (DBG) log("onServiceConnected");
-            mIDataService = IDataService.Stub.asInterface(service);
-            mDeathRecipient = new DataServiceManagerDeathRecipient();
-            mBound = true;
-            mLastBoundPackageName = getDataServicePackageName();
-            removeMessages(EVENT_WATCHDOG_TIMEOUT);
-
-            try {
-                service.linkToDeath(mDeathRecipient, 0);
-                mIDataService.createDataServiceProvider(mPhone.getPhoneId());
-                mIDataService.registerForDataCallListChanged(mPhone.getPhoneId(),
-                        new CellularDataServiceCallback("dataCallListChanged"));
-                mIDataService.registerForUnthrottleApn(mPhone.getPhoneId(),
-                        new CellularDataServiceCallback("unthrottleApn"));
-            } catch (RemoteException e) {
-                loge("Remote exception. " + e);
-                return;
-            }
-            mServiceBindingChangedRegistrants.notifyResult(true);
-        }
-        @Override
-        public void onServiceDisconnected(ComponentName name) {
-            if (DBG) log("onServiceDisconnected");
-            removeMessages(EVENT_WATCHDOG_TIMEOUT);
-            mIDataService = null;
-            mBound = false;
-            mServiceBindingChangedRegistrants.notifyResult(false);
-            mTargetBindingPackageName = null;
-        }
-    }
-
-    private final class CellularDataServiceCallback extends IDataServiceCallback.Stub {
-
-        private final String mTag;
-
-        CellularDataServiceCallback(String tag) {
-            mTag = tag;
-        }
-
-        public String getTag() {
-            return mTag;
-        }
-
-        @Override
-        public void onSetupDataCallComplete(@DataServiceCallback.ResultCode int resultCode,
-                                            DataCallResponse response) {
-            if (DBG) {
-                log("onSetupDataCallComplete. resultCode = " + resultCode + ", response = "
-                        + response);
-            }
-            removeMessages(EVENT_WATCHDOG_TIMEOUT, CellularDataServiceCallback.this);
-            Message msg = mMessageMap.remove(asBinder());
-            if (msg != null) {
-                msg.getData().putParcelable(DATA_CALL_RESPONSE, response);
-                sendCompleteMessage(msg, resultCode);
-            } else {
-                loge("Unable to find the message for setup call response.");
-            }
-        }
-
-        @Override
-        public void onDeactivateDataCallComplete(@DataServiceCallback.ResultCode int resultCode) {
-            if (DBG) log("onDeactivateDataCallComplete. resultCode = " + resultCode);
-            removeMessages(EVENT_WATCHDOG_TIMEOUT, CellularDataServiceCallback.this);
-            Message msg = mMessageMap.remove(asBinder());
-            sendCompleteMessage(msg, resultCode);
-        }
-
-        @Override
-        public void onSetInitialAttachApnComplete(@DataServiceCallback.ResultCode int resultCode) {
-            if (DBG) log("onSetInitialAttachApnComplete. resultCode = " + resultCode);
-            Message msg = mMessageMap.remove(asBinder());
-            sendCompleteMessage(msg, resultCode);
-        }
-
-        @Override
-        public void onSetDataProfileComplete(@DataServiceCallback.ResultCode int resultCode) {
-            if (DBG) log("onSetDataProfileComplete. resultCode = " + resultCode);
-            Message msg = mMessageMap.remove(asBinder());
-            sendCompleteMessage(msg, resultCode);
-        }
-
-        @Override
-        public void onRequestDataCallListComplete(@DataServiceCallback.ResultCode int resultCode,
-                                              List<DataCallResponse> dataCallList) {
-            if (DBG) log("onRequestDataCallListComplete. resultCode = " + resultCode);
-            Message msg = mMessageMap.remove(asBinder());
-            sendCompleteMessage(msg, resultCode);
-        }
-
-        @Override
-        public void onDataCallListChanged(List<DataCallResponse> dataCallList) {
-            mDataCallListChangedRegistrants.notifyRegistrants(
-                    new AsyncResult(null, dataCallList, null));
-        }
-
-        @Override
-        public void onHandoverStarted(@DataServiceCallback.ResultCode int resultCode) {
-            if (DBG) log("onHandoverStarted. resultCode = " + resultCode);
-            removeMessages(EVENT_WATCHDOG_TIMEOUT, CellularDataServiceCallback.this);
-            Message msg = mMessageMap.remove(asBinder());
-            sendCompleteMessage(msg, resultCode);
-        }
-
-        @Override
-        public void onHandoverCancelled(@DataServiceCallback.ResultCode int resultCode) {
-            if (DBG) log("onHandoverCancelled. resultCode = " + resultCode);
-            removeMessages(EVENT_WATCHDOG_TIMEOUT, CellularDataServiceCallback.this);
-            Message msg = mMessageMap.remove(asBinder());
-            sendCompleteMessage(msg, resultCode);
-        }
-
-        @Override
-        public void onApnUnthrottled(String apn) {
-            if (apn != null) {
-                mApnUnthrottledRegistrants.notifyRegistrants(
-                        new AsyncResult(null, apn, null));
-            } else {
-                loge("onApnUnthrottled: apn is null");
-            }
-        }
-
-        @Override
-        public void onDataProfileUnthrottled(DataProfile dataProfile) {
-            if (dataProfile != null) {
-                mApnUnthrottledRegistrants.notifyRegistrants(
-                        new AsyncResult(null, dataProfile, null));
-            } else {
-                loge("onDataProfileUnthrottled: dataProfile is null");
-            }
-        }
-    }
-
-    /**
-     * Constructor
-     *
-     * @param phone The phone object
-     * @param transportType The transport type
-     * @param tagSuffix Logging tag suffix
-     */
-    public DataServiceManager(Phone phone, @TransportType int transportType, String tagSuffix) {
-        mPhone = phone;
-        mTag = "DSM" + tagSuffix;
-        mTransportType = transportType;
-        mBound = false;
-        mCarrierConfigManager = (CarrierConfigManager) phone.getContext().getSystemService(
-                Context.CARRIER_CONFIG_SERVICE);
-        // NOTE: Do NOT use AppGlobals to retrieve the permission manager; AppGlobals
-        // caches the service instance, but we need to explicitly request a new service
-        // so it can be mocked out for tests
-        mPermissionManager = (LegacyPermissionManager) phone.getContext().getSystemService(
-                Context.LEGACY_PERMISSION_SERVICE);
-        mAppOps = (AppOpsManager) phone.getContext().getSystemService(Context.APP_OPS_SERVICE);
-
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        try {
-            Context contextAsUser = phone.getContext().createPackageContextAsUser(
-                phone.getContext().getPackageName(), 0, UserHandle.ALL);
-            contextAsUser.registerReceiver(mBroadcastReceiver, intentFilter,
-                null /* broadcastPermission */, null);
-        } catch (PackageManager.NameNotFoundException e) {
-            loge("Package name not found: " + e.getMessage());
-        }
-
-        PhoneConfigurationManager.registerForMultiSimConfigChange(
-                this, EVENT_BIND_DATA_SERVICE, null);
-
-        sendEmptyMessage(EVENT_BIND_DATA_SERVICE);
-    }
-
-    /**
-     * Handle message events
-     *
-     * @param msg The message to handle
-     */
-    @Override
-    public void handleMessage(Message msg) {
-        switch (msg.what) {
-            case EVENT_BIND_DATA_SERVICE:
-                rebindDataService();
-                break;
-            case EVENT_WATCHDOG_TIMEOUT:
-                handleRequestUnresponded((CellularDataServiceCallback) msg.obj);
-                break;
-            default:
-                loge("Unhandled event " + msg.what);
-        }
-    }
-
-    private void handleRequestUnresponded(CellularDataServiceCallback callback) {
-        String message = "Request " + callback.getTag() + " unresponded on transport "
-                + AccessNetworkConstants.transportTypeToString(mTransportType) + " in "
-                + REQUEST_UNRESPONDED_TIMEOUT / 1000 + " seconds.";
-        log(message);
-        // Using fixed UUID to avoid duplicate bugreport notification
-        AnomalyReporter.reportAnomaly(
-                UUID.fromString("f5d5cbe6-9bd6-4009-b764-42b1b649b1de"),
-                message, mPhone.getCarrierId());
-    }
-
-    private void unbindDataService() {
-        // Start by cleaning up all packages that *shouldn't* have permissions.
-        revokePermissionsFromUnusedDataServices();
-        if (mIDataService != null && mIDataService.asBinder().isBinderAlive()) {
-            log("unbinding service");
-            // Remove the network availability updater and then unbind the service.
-            try {
-                mIDataService.removeDataServiceProvider(mPhone.getPhoneId());
-            } catch (RemoteException e) {
-                loge("Cannot remove data service provider. " + e);
-            }
-        }
-
-        if (mServiceConnection != null) {
-            mPhone.getContext().unbindService(mServiceConnection);
-        }
-        mIDataService = null;
-        mServiceConnection = null;
-        mTargetBindingPackageName = null;
-        mBound = false;
-    }
-
-    private void bindDataService(String packageName) {
-        if (mPhone == null || !SubscriptionManager.isValidPhoneId(mPhone.getPhoneId())) {
-            loge("can't bindDataService with invalid phone or phoneId.");
-            return;
-        }
-
-        if (TextUtils.isEmpty(packageName)) {
-            loge("Can't find the binding package");
-            return;
-        }
-
-        Intent intent = null;
-        String className = getDataServiceClassName();
-        if (TextUtils.isEmpty(className)) {
-            intent = new Intent(DataService.SERVICE_INTERFACE);
-            intent.setPackage(packageName);
-        } else {
-            ComponentName cm = new ComponentName(packageName, className);
-            intent = new Intent(DataService.SERVICE_INTERFACE).setComponent(cm);
-        }
-
-        // Then pre-emptively grant the permissions to the package we will bind.
-        grantPermissionsToService(packageName);
-
-        try {
-            mServiceConnection = new CellularDataServiceConnection();
-            if (!mPhone.getContext().bindService(
-                    intent, mServiceConnection, Context.BIND_AUTO_CREATE)) {
-                loge("Cannot bind to the data service.");
-                return;
-            }
-            mTargetBindingPackageName = packageName;
-        } catch (Exception e) {
-            loge("Cannot bind to the data service. Exception: " + e);
-        }
-    }
-
-    private void rebindDataService() {
-        String packageName = getDataServicePackageName();
-        // Do nothing if no need to rebind.
-        if (SubscriptionManager.isValidPhoneId(mPhone.getPhoneId())
-                && TextUtils.equals(packageName, mTargetBindingPackageName)) {
-            if (DBG) log("Service " + packageName + " already bound or being bound.");
-            return;
-        }
-
-        unbindDataService();
-        bindDataService(packageName);
-    }
-
-    @NonNull
-    private Set<String> getAllDataServicePackageNames() {
-        // Cowardly using the public PackageManager interface here.
-        // Note: This matches only packages that were installed on the system image. If we ever
-        // expand the permissions model to allow CarrierPrivileged packages, then this will need
-        // to be updated.
-        List<ResolveInfo> dataPackages =
-                mPhone.getContext().getPackageManager().queryIntentServices(
-                        new Intent(DataService.SERVICE_INTERFACE),
-                                PackageManager.MATCH_SYSTEM_ONLY);
-        HashSet<String> packageNames = new HashSet<>();
-        for (ResolveInfo info : dataPackages) {
-            if (info.serviceInfo == null) continue;
-            packageNames.add(info.serviceInfo.packageName);
-        }
-        return packageNames;
-    }
-
-    /**
-     * Get the data service package name for our current transport type.
-     *
-     * @return package name of the data service package for the the current transportType.
-     */
-    public String getDataServicePackageName() {
-        return getDataServicePackageName(mTransportType);
-    }
-
-    /**
-     * Get the data service package by transport type.
-     *
-     * When we bind to a DataService package, we need to revoke permissions from stale
-     * packages; we need to exclude data packages for all transport types, so we need to
-     * to be able to query by transport type.
-     *
-     * @param transportType The transport type
-     * @return package name of the data service package for the specified transportType.
-     */
-    private String getDataServicePackageName(@TransportType int transportType) {
-        String packageName;
-        int resourceId;
-        String carrierConfig;
-
-        switch (transportType) {
-            case AccessNetworkConstants.TRANSPORT_TYPE_WWAN:
-                resourceId = com.android.internal.R.string.config_wwan_data_service_package;
-                carrierConfig = CarrierConfigManager
-                        .KEY_CARRIER_DATA_SERVICE_WWAN_PACKAGE_OVERRIDE_STRING;
-                break;
-            case AccessNetworkConstants.TRANSPORT_TYPE_WLAN:
-                resourceId = com.android.internal.R.string.config_wlan_data_service_package;
-                carrierConfig = CarrierConfigManager
-                        .KEY_CARRIER_DATA_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING;
-                break;
-            default:
-                throw new IllegalStateException("Transport type not WWAN or WLAN. type="
-                        + AccessNetworkConstants.transportTypeToString(mTransportType));
-        }
-
-        // Read package name from resource overlay
-        packageName = mPhone.getContext().getResources().getString(resourceId);
-
-        PersistableBundle b = mCarrierConfigManager.getConfigForSubId(mPhone.getSubId());
-
-        if (b != null && !TextUtils.isEmpty(b.getString(carrierConfig))) {
-            // If carrier config overrides it, use the one from carrier config
-            packageName = b.getString(carrierConfig, packageName);
-        }
-
-        return packageName;
-    }
-
-    /**
-     * Get the data service class name for our current transport type.
-     *
-     * @return class name of the data service package for the the current transportType.
-     */
-    private String getDataServiceClassName() {
-        return getDataServiceClassName(mTransportType);
-    }
-
-
-    /**
-     * Get the data service class by transport type.
-     *
-     * @param transportType either WWAN or WLAN
-     * @return class name of the data service package for the specified transportType.
-     */
-    private String getDataServiceClassName(int transportType) {
-        String className;
-        int resourceId;
-        String carrierConfig;
-        switch (transportType) {
-            case AccessNetworkConstants.TRANSPORT_TYPE_WWAN:
-                resourceId = com.android.internal.R.string.config_wwan_data_service_class;
-                carrierConfig = CarrierConfigManager
-                        .KEY_CARRIER_DATA_SERVICE_WWAN_CLASS_OVERRIDE_STRING;
-                break;
-            case AccessNetworkConstants.TRANSPORT_TYPE_WLAN:
-                resourceId = com.android.internal.R.string.config_wlan_data_service_class;
-                carrierConfig = CarrierConfigManager
-                        .KEY_CARRIER_DATA_SERVICE_WLAN_CLASS_OVERRIDE_STRING;
-                break;
-            default:
-                throw new IllegalStateException("Transport type not WWAN or WLAN. type="
-                        + transportType);
-        }
-
-        // Read package name from resource overlay
-        className = mPhone.getContext().getResources().getString(resourceId);
-
-        PersistableBundle b = mCarrierConfigManager.getConfigForSubId(mPhone.getSubId());
-
-        if (b != null && !TextUtils.isEmpty(b.getString(carrierConfig))) {
-            // If carrier config overrides it, use the one from carrier config
-            className = b.getString(carrierConfig, className);
-        }
-
-        return className;
-    }
-
-    private void sendCompleteMessage(Message msg, @DataServiceCallback.ResultCode int code) {
-        if (msg != null) {
-            msg.arg1 = code;
-            msg.sendToTarget();
-        }
-    }
-
-    /**
-     * Setup a data connection. The data service provider must implement this method to support
-     * establishing a packet data connection. When completed or error, the service must invoke
-     * the provided callback to notify the platform.
-     *
-     * @param accessNetworkType Access network type that the data call will be established on.
-     *        Must be one of {@link AccessNetworkConstants.AccessNetworkType}.
-     * @param dataProfile Data profile used for data call setup. See {@link DataProfile}
-     * @param isRoaming True if the device is data roaming.
-     * @param allowRoaming True if data roaming is allowed by the user.
-     * @param reason The reason for data setup. Must be {@link DataService#REQUEST_REASON_NORMAL} or
-     *        {@link DataService#REQUEST_REASON_HANDOVER}.
-     * @param linkProperties If {@code reason} is {@link DataService#REQUEST_REASON_HANDOVER}, this
-     *        is the link properties of the existing data connection, otherwise null.
-     * @param pduSessionId The pdu session id to be used for this data call.  A value of -1 means
-     *                     no pdu session id was attached to this call.
-     *                     Reference: 3GPP TS 24.007 Section 11.2.3.1b
-     * @param sliceInfo The slice that represents S-NSSAI.
-     *                  Reference: 3GPP TS 24.501
-     * @param trafficDescriptor The traffic descriptor for this data call, used for URSP matching.
-     *                          Reference: 3GPP TS TS 24.526 Section 5.2
-     * @param matchAllRuleAllowed True if using the default match-all URSP rule for this request is
-     *                            allowed.
-     * @param onCompleteMessage The result message for this request. Null if the client does not
-     *        care about the result.
-     */
-    public void setupDataCall(int accessNetworkType, DataProfile dataProfile, boolean isRoaming,
-            boolean allowRoaming, int reason, LinkProperties linkProperties, int pduSessionId,
-            @Nullable NetworkSliceInfo sliceInfo, @Nullable TrafficDescriptor trafficDescriptor,
-            boolean matchAllRuleAllowed, Message onCompleteMessage) {
-        if (DBG) log("setupDataCall");
-        if (!mBound) {
-            loge("setupDataCall: Data service not bound.");
-            sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
-            return;
-        }
-
-        CellularDataServiceCallback callback = new CellularDataServiceCallback("setupDataCall");
-        if (onCompleteMessage != null) {
-            mMessageMap.put(callback.asBinder(), onCompleteMessage);
-        }
-        try {
-            sendMessageDelayed(obtainMessage(EVENT_WATCHDOG_TIMEOUT, callback),
-                    REQUEST_UNRESPONDED_TIMEOUT);
-            mIDataService.setupDataCall(mPhone.getPhoneId(), accessNetworkType, dataProfile,
-                    isRoaming, allowRoaming, reason, linkProperties, pduSessionId, sliceInfo,
-                    trafficDescriptor, matchAllRuleAllowed, callback);
-        } catch (RemoteException e) {
-            loge("setupDataCall: Cannot invoke setupDataCall on data service.");
-            mMessageMap.remove(callback.asBinder());
-            sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
-        }
-    }
-
-    /**
-     * Deactivate a data connection. The data service provider must implement this method to
-     * support data connection tear down. When completed or error, the service must invoke the
-     * provided callback to notify the platform.
-     *
-     * @param cid Call id returned in the callback of {@link #setupDataCall(int, DataProfile,
-     *        boolean, boolean, int, LinkProperties, Message)}
-     * @param reason The reason for data deactivation. Must be
-     *        {@link DataService#REQUEST_REASON_NORMAL}, {@link DataService#REQUEST_REASON_SHUTDOWN}
-     *        or {@link DataService#REQUEST_REASON_HANDOVER}.
-     * @param onCompleteMessage The result message for this request. Null if the client does not
-     *        care about the result.
-     */
-    public void deactivateDataCall(int cid, int reason, Message onCompleteMessage) {
-        if (DBG) log("deactivateDataCall");
-        if (!mBound) {
-            loge("Data service not bound.");
-            sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
-            return;
-        }
-
-        CellularDataServiceCallback callback =
-                new CellularDataServiceCallback("deactivateDataCall");
-        if (onCompleteMessage != null) {
-            mMessageMap.put(callback.asBinder(), onCompleteMessage);
-        }
-        try {
-            sendMessageDelayed(obtainMessage(EVENT_WATCHDOG_TIMEOUT, callback),
-                    REQUEST_UNRESPONDED_TIMEOUT);
-            mIDataService.deactivateDataCall(mPhone.getPhoneId(), cid, reason, callback);
-        } catch (RemoteException e) {
-            loge("Cannot invoke deactivateDataCall on data service.");
-            mMessageMap.remove(callback.asBinder());
-            sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
-        }
-    }
-
-    /**
-     * Indicates that a handover has begun.  This is called on the source transport.
-     *
-     * Any resources being transferred cannot be released while a
-     * handover is underway.
-     *
-     * If a handover was unsuccessful, then the framework calls DataServiceManager#cancelHandover.
-     * The target transport retains ownership over any of the resources being transferred.
-     *
-     * If a handover was successful, the framework calls DataServiceManager#deactivateDataCall with
-     * reason HANDOVER. The target transport now owns the transferred resources and is
-     * responsible for releasing them.
-     *
-     * @param cid The identifier of the data call which is provided in DataCallResponse
-     * @param onCompleteMessage The result callback for this request.
-     */
-    public void startHandover(int cid, @NonNull Message onCompleteMessage) {
-        CellularDataServiceCallback callback =
-                setupCallbackHelper("startHandover", onCompleteMessage);
-        if (callback == null) {
-            loge("startHandover: callback == null");
-            sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
-            return;
-        }
-
-        try {
-            sendMessageDelayed(obtainMessage(EVENT_WATCHDOG_TIMEOUT, callback),
-                    REQUEST_UNRESPONDED_TIMEOUT);
-            mIDataService.startHandover(mPhone.getPhoneId(), cid, callback);
-        } catch (RemoteException e) {
-            loge("Cannot invoke startHandover on data service.");
-            mMessageMap.remove(callback.asBinder());
-            sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
-        }
-    }
-
-    /**
-     * Indicates that a handover was cancelled after a call to DataServiceManager#startHandover.
-     * This is called on the source transport.
-     *
-     * Since the handover was unsuccessful, the source transport retains ownership over any of
-     * the resources being transferred and is still responsible for releasing them.
-     *
-     * @param cid The identifier of the data call which is provided in DataCallResponse
-     * @param onCompleteMessage The result callback for this request.
-     */
-    public void cancelHandover(int cid, @NonNull Message onCompleteMessage) {
-        CellularDataServiceCallback callback =
-                setupCallbackHelper("cancelHandover", onCompleteMessage);
-        if (callback == null) {
-            sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
-            return;
-        }
-
-        try {
-            sendMessageDelayed(obtainMessage(EVENT_WATCHDOG_TIMEOUT, callback),
-                    REQUEST_UNRESPONDED_TIMEOUT);
-            mIDataService.cancelHandover(mPhone.getPhoneId(), cid, callback);
-        } catch (RemoteException e) {
-            loge("Cannot invoke cancelHandover on data service.");
-            mMessageMap.remove(callback.asBinder());
-            sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
-        }
-    }
-
-    @Nullable
-    private CellularDataServiceCallback setupCallbackHelper(
-            @NonNull final String operationName, @NonNull final Message onCompleteMessage) {
-        if (DBG) log(operationName);
-        if (!mBound) {
-            sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
-            return null;
-        }
-
-        CellularDataServiceCallback callback =
-                new CellularDataServiceCallback(operationName);
-        if (onCompleteMessage != null) {
-            if (DBG) log(operationName + ": onCompleteMessage set");
-            mMessageMap.put(callback.asBinder(), onCompleteMessage);
-        } else {
-            if (DBG) log(operationName + ": onCompleteMessage not set");
-        }
-        return callback;
-    }
-
-    /**
-     * Set an APN to initial attach network.
-     *
-     * @param dataProfile Data profile used for data call setup. See {@link DataProfile}.
-     * @param isRoaming True if the device is data roaming.
-     * @param onCompleteMessage The result message for this request. Null if the client does not
-     *        care about the result.
-     */
-    public void setInitialAttachApn(DataProfile dataProfile, boolean isRoaming,
-                                    Message onCompleteMessage) {
-        if (DBG) log("setInitialAttachApn");
-        if (!mBound) {
-            loge("Data service not bound.");
-            sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
-            return;
-        }
-
-        CellularDataServiceCallback callback =
-                new CellularDataServiceCallback("setInitialAttachApn");
-        if (onCompleteMessage != null) {
-            mMessageMap.put(callback.asBinder(), onCompleteMessage);
-        }
-        try {
-            mIDataService.setInitialAttachApn(mPhone.getPhoneId(), dataProfile, isRoaming,
-                    callback);
-        } catch (RemoteException e) {
-            loge("Cannot invoke setInitialAttachApn on data service.");
-            mMessageMap.remove(callback.asBinder());
-            sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
-        }
-    }
-
-    /**
-     * Send current carrier's data profiles to the data service for data call setup. This is
-     * only for CDMA carrier that can change the profile through OTA. The data service should
-     * always uses the latest data profile sent by the framework.
-     *
-     * @param dps A list of data profiles.
-     * @param isRoaming True if the device is data roaming.
-     * @param onCompleteMessage The result message for this request. Null if the client does not
-     *        care about the result.
-     */
-    public void setDataProfile(List<DataProfile> dps, boolean isRoaming,
-                               Message onCompleteMessage) {
-        if (DBG) log("setDataProfile");
-        if (!mBound) {
-            loge("Data service not bound.");
-            sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
-            return;
-        }
-
-        CellularDataServiceCallback callback = new CellularDataServiceCallback("setDataProfile");
-        if (onCompleteMessage != null) {
-            mMessageMap.put(callback.asBinder(), onCompleteMessage);
-        }
-        try {
-            mIDataService.setDataProfile(mPhone.getPhoneId(), dps, isRoaming, callback);
-        } catch (RemoteException e) {
-            loge("Cannot invoke setDataProfile on data service.");
-            mMessageMap.remove(callback.asBinder());
-            sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
-        }
-    }
-
-    /**
-     * Get the active data call list.
-     *
-     * @param onCompleteMessage The result message for this request. Null if the client does not
-     *        care about the result.
-     */
-    public void requestDataCallList(Message onCompleteMessage) {
-        if (DBG) log("requestDataCallList");
-        if (!mBound) {
-            loge("Data service not bound.");
-            sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
-            return;
-        }
-
-        CellularDataServiceCallback callback =
-                new CellularDataServiceCallback("requestDataCallList");
-        if (onCompleteMessage != null) {
-            mMessageMap.put(callback.asBinder(), onCompleteMessage);
-        }
-        try {
-            mIDataService.requestDataCallList(mPhone.getPhoneId(), callback);
-        } catch (RemoteException e) {
-            loge("Cannot invoke requestDataCallList on data service.");
-            if (callback != null) {
-                mMessageMap.remove(callback.asBinder());
-            }
-            sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
-        }
-    }
-
-    /**
-     * Register for data call list changed event.
-     *
-     * @param h The target to post the event message to.
-     * @param what The event.
-     */
-    public void registerForDataCallListChanged(Handler h, int what) {
-        if (h != null) {
-            mDataCallListChangedRegistrants.addUnique(h, what, null);
-        }
-    }
-
-    /**
-     * Unregister for data call list changed event.
-     *
-     * @param h The handler
-     */
-    public void unregisterForDataCallListChanged(Handler h) {
-        if (h != null) {
-            mDataCallListChangedRegistrants.remove(h);
-        }
-    }
-
-    /**
-     * Register apn unthrottled event
-     *
-     * @param h The target to post the event message to.
-     * @param what The event.
-     */
-    public void registerForApnUnthrottled(Handler h, int what) {
-        if (h != null) {
-            mApnUnthrottledRegistrants.addUnique(h, what, null);
-        }
-    }
-
-    /**
-     * Unregister for apn unthrottled event
-     *
-     * @param h The handler
-     */
-    public void unregisterForApnUnthrottled(Handler h) {
-        if (h != null) {
-            mApnUnthrottledRegistrants.remove(h);
-        }
-    }
-
-    /**
-     * Register for data service binding status changed event.
-     *
-     * @param h The target to post the event message to.
-     * @param what The event.
-     * @param obj The user object.
-     */
-    public void registerForServiceBindingChanged(Handler h, int what, Object obj) {
-        if (h != null) {
-            mServiceBindingChangedRegistrants.addUnique(h, what, obj);
-        }
-
-    }
-
-    /**
-     * Unregister for data service binding status changed event.
-     *
-     * @param h The handler
-     */
-    public void unregisterForServiceBindingChanged(Handler h) {
-        if (h != null) {
-            mServiceBindingChangedRegistrants.remove(h);
-        }
-    }
-
-    /**
-     * Get the transport type. Must be a {@link TransportType}.
-     *
-     * @return
-     */
-    @TransportType
-    public int getTransportType() {
-        return mTransportType;
-    }
-
-    private void log(String s) {
-        Rlog.d(mTag, s);
-    }
-
-    private void loge(String s) {
-        Rlog.e(mTag, s);
-    }
-}
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataThrottler.java b/src/java/com/android/internal/telephony/dataconnection/DataThrottler.java
deleted file mode 100644
index 4a465d2..0000000
--- a/src/java/com/android/internal/telephony/dataconnection/DataThrottler.java
+++ /dev/null
@@ -1,366 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.telephony.dataconnection;
-
-import android.annotation.ElapsedRealtimeLong;
-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.Message;
-import android.os.PersistableBundle;
-import android.telephony.AccessNetworkConstants;
-import android.telephony.Annotation;
-import android.telephony.Annotation.ApnType;
-import android.telephony.CarrierConfigManager;
-import android.telephony.SubscriptionManager;
-import android.telephony.data.ApnSetting;
-import android.telephony.data.ThrottleStatus;
-
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.RetryManager;
-import com.android.telephony.Rlog;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Data throttler tracks the throttling status of the data network and notifies registrants when
- * there are changes.  The throttler is per phone and per transport type.
- */
-public class DataThrottler extends Handler {
-    private static final String TAG = DataThrottler.class.getSimpleName();
-
-    private static final int EVENT_SET_RETRY_TIME = 1;
-    private static final int EVENT_CARRIER_CONFIG_CHANGED = 2;
-    private static final int EVENT_RESET = 3;
-    private static final int EVENT_AIRPLANE_MODE_CHANGED = 4;
-    private static final int EVENT_TRACING_AREA_CODE_CHANGED = 5;
-
-    private final Phone mPhone;
-    private final int mSlotIndex;
-    private final @AccessNetworkConstants.TransportType int mTransportType;
-    private boolean mResetWhenAreaCodeChanged = false;
-
-    /**
-     * Callbacks that report the apn throttle status.
-     */
-    private final List<DataThrottler.Callback> mCallbacks = new ArrayList<>();
-
-    /**
-     * Keeps track of detailed information of the throttle status that is meant to be
-     * reported to other components.
-     */
-    private final Map<Integer, ThrottleStatus> mThrottleStatus = new ConcurrentHashMap<>();
-
-    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
-                if (mPhone.getPhoneId() == intent.getIntExtra(CarrierConfigManager.EXTRA_SLOT_INDEX,
-                        SubscriptionManager.INVALID_SIM_SLOT_INDEX)) {
-                    if (intent.getBooleanExtra(
-                            CarrierConfigManager.EXTRA_REBROADCAST_ON_UNLOCK, false)) {
-                        // Ignore the rebroadcast one to prevent multiple carrier config changed
-                        // event during boot up.
-                        return;
-                    }
-                    int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
-                            SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-                    if (SubscriptionManager.isValidSubscriptionId(subId)) {
-                        sendEmptyMessage(EVENT_CARRIER_CONFIG_CHANGED);
-                    }
-                }
-            }
-        }
-    };
-
-    public DataThrottler(Phone phone, int transportType) {
-        super(null, false);
-        mPhone = phone;
-        mSlotIndex = phone.getPhoneId();
-        mTransportType = transportType;
-
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        mPhone.getContext().registerReceiver(mBroadcastReceiver, filter, null, mPhone);
-
-        mPhone.getServiceStateTracker().registerForAirplaneModeChanged(this,
-                EVENT_AIRPLANE_MODE_CHANGED, null);
-        mPhone.getServiceStateTracker().registerForAreaCodeChanged(this,
-                EVENT_TRACING_AREA_CODE_CHANGED, null);
-    }
-
-    @Override
-    public void handleMessage(Message msg) {
-        AsyncResult ar;
-        switch (msg.what) {
-            case EVENT_SET_RETRY_TIME:
-                int apnTypes = msg.arg1;
-                int newRequestType = msg.arg2;
-                long retryElapsedTime = (long) msg.obj;
-                setRetryTimeInternal(apnTypes, retryElapsedTime, newRequestType);
-                break;
-            case EVENT_CARRIER_CONFIG_CHANGED:
-                onCarrierConfigChanged();
-                break;
-            case EVENT_RESET:
-                resetInternal();
-                break;
-            case EVENT_AIRPLANE_MODE_CHANGED:
-                ar = (AsyncResult) msg.obj;
-                if (!(Boolean) ar.result) {
-                    resetInternal();
-                }
-                break;
-            case EVENT_TRACING_AREA_CODE_CHANGED:
-                if (mResetWhenAreaCodeChanged) {
-                    resetInternal();
-                }
-                break;
-        }
-    }
-
-    @NonNull
-    private PersistableBundle getCarrierConfig() {
-        CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
-                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        if (configManager != null) {
-            // If an invalid subId is used, this bundle will contain default values.
-            PersistableBundle config = configManager.getConfigForSubId(mPhone.getSubId());
-            if (config != null) {
-                return config;
-            }
-        }
-        // Return static default defined in CarrierConfigManager.
-        return CarrierConfigManager.getDefaultConfig();
-    }
-
-    private void onCarrierConfigChanged() {
-        PersistableBundle config = getCarrierConfig();
-        mResetWhenAreaCodeChanged = config.getBoolean(
-                CarrierConfigManager.KEY_UNTHROTTLE_DATA_RETRY_WHEN_TAC_CHANGES_BOOL, false);
-    }
-
-    /**
-     * Set the retry time and handover failure mode for the give APN types.
-     *
-     * @param apnTypes APN types
-     * @param retryElapsedTime The elapsed time that data connection for APN types should not be
-     * retried. {@link RetryManager#NO_SUGGESTED_RETRY_DELAY} indicates throttling does not exist.
-     * {@link RetryManager#NO_RETRY} indicates retry should never happen.
-     */
-    public void setRetryTime(@ApnType int apnTypes, @ElapsedRealtimeLong long retryElapsedTime,
-            @DcTracker.RequestNetworkType int newRequestType) {
-        sendMessage(obtainMessage(EVENT_SET_RETRY_TIME, apnTypes, newRequestType,
-                retryElapsedTime));
-    }
-
-    /**
-     * Set the retry time and handover failure mode for the give APN types. This is running on the
-     * handler thread.
-     *
-     * @param apnTypes APN types
-     * @param retryElapsedTime The elapsed time that data connection for APN types should not be
-     * retried. {@link RetryManager#NO_SUGGESTED_RETRY_DELAY} indicates throttling does not exist.
-     * {@link RetryManager#NO_RETRY} indicates retry should never happen.
-     */
-    private void setRetryTimeInternal(@ApnType int apnTypes, @ElapsedRealtimeLong
-            long retryElapsedTime, @DcTracker.RequestNetworkType int newRequestType) {
-        if (retryElapsedTime < 0) {
-            retryElapsedTime = RetryManager.NO_SUGGESTED_RETRY_DELAY;
-        }
-
-        List<ThrottleStatus> changedStatuses = new ArrayList<>();
-        while (apnTypes != 0) {
-            int apnType;
-            // Due to an API mistake of ApnSetting.TYPE_DEFAULT (which combines default and hipri 
-            // bit), we need to do special handling here.
-            if ((apnTypes & ApnSetting.TYPE_DEFAULT) == ApnSetting.TYPE_DEFAULT) {
-                apnType = ApnSetting.TYPE_DEFAULT;
-                apnTypes &= ~ApnSetting.TYPE_DEFAULT;
-            } else {
-                //Extract the least significant bit.
-                apnType = apnTypes & -apnTypes;
-                //Remove the least significant bit.
-                apnTypes &= apnTypes - 1;
-            }
-
-            //Update the apn throttle status
-            ThrottleStatus newStatus = createStatus(apnType, retryElapsedTime, newRequestType);
-
-            ThrottleStatus oldStatus = mThrottleStatus.get(apnType);
-
-            //Check to see if there is a change that needs to be applied
-            if (!newStatus.equals(oldStatus)) {
-                //Mark as changed status
-                changedStatuses.add(newStatus);
-
-                //Put the new status in the temp space
-                mThrottleStatus.put(apnType, newStatus);
-            }
-        }
-
-        if (changedStatuses.size() > 0) {
-            sendThrottleStatusChanged(changedStatuses);
-        }
-    }
-
-    /**
-     * Get the earliest retry time for the given APN type. The time is the system's elapse time.
-     *
-     * Note the DataThrottler is running phone process's main thread, which is most of the telephony
-     * components running on. Calling this method from other threads might run into race conditions.
-     *
-     * @param apnType APN type
-     * @return The earliest retry time for APN type. The time is the system's elapse time.
-     * {@link RetryManager#NO_SUGGESTED_RETRY_DELAY} indicates there is no throttling for given APN
-     * type, {@link RetryManager#NO_RETRY} indicates retry should never happen.
-     */
-    @ElapsedRealtimeLong
-    public long getRetryTime(@ApnType int apnType) {
-        ThrottleStatus status = mThrottleStatus.get(apnType);
-        if (status != null) {
-            if (status.getThrottleType() == ThrottleStatus.THROTTLE_TYPE_NONE) {
-                return RetryManager.NO_SUGGESTED_RETRY_DELAY;
-            } else {
-                return status.getThrottleExpiryTimeMillis();
-            }
-        }
-        return RetryManager.NO_SUGGESTED_RETRY_DELAY;
-    }
-
-    /**
-     * Resets retry times for all APNs to {@link RetryManager.NO_SUGGESTED_RETRY_DELAY}.
-     */
-    public void reset() {
-        sendEmptyMessage(EVENT_RESET);
-    }
-
-    /**
-     * Resets retry times for all APNs to {@link RetryManager.NO_SUGGESTED_RETRY_DELAY}.
-     */
-    private void resetInternal() {
-        final List<Integer> apnTypes = new ArrayList<>();
-        for (ThrottleStatus throttleStatus : mThrottleStatus.values()) {
-            apnTypes.add(throttleStatus.getApnType());
-        }
-
-        for (int apnType : apnTypes) {
-            setRetryTime(apnType, RetryManager.NO_SUGGESTED_RETRY_DELAY,
-                    DcTracker.REQUEST_TYPE_NORMAL);
-        }
-    }
-
-    private ThrottleStatus createStatus(@Annotation.ApnType int apnType, long retryElapsedTime,
-            @DcTracker.RequestNetworkType int newRequestType) {
-        ThrottleStatus.Builder builder = new ThrottleStatus.Builder();
-
-        if (retryElapsedTime == RetryManager.NO_SUGGESTED_RETRY_DELAY) {
-            builder
-                    .setNoThrottle()
-                    .setRetryType(getRetryType(newRequestType));
-        } else if (retryElapsedTime == RetryManager.NO_RETRY) {
-            builder
-                    .setThrottleExpiryTimeMillis(RetryManager.NO_RETRY)
-                    .setRetryType(ThrottleStatus.RETRY_TYPE_NONE);
-        } else {
-            builder
-                    .setThrottleExpiryTimeMillis(retryElapsedTime)
-                    .setRetryType(getRetryType(newRequestType));
-        }
-        return builder
-                .setSlotIndex(mSlotIndex)
-                .setTransportType(mTransportType)
-                .setApnType(apnType)
-                .build();
-    }
-
-    private static int getRetryType(@DcTracker.RequestNetworkType int newRequestType) {
-        if (newRequestType == DcTracker.REQUEST_TYPE_NORMAL) {
-            return ThrottleStatus.RETRY_TYPE_NEW_CONNECTION;
-        }
-
-        if (newRequestType == DcTracker.REQUEST_TYPE_HANDOVER) {
-            return  ThrottleStatus.RETRY_TYPE_HANDOVER;
-        }
-
-        loge("createStatus: Unknown requestType=" + newRequestType);
-        return ThrottleStatus.RETRY_TYPE_NEW_CONNECTION;
-    }
-
-    private void sendThrottleStatusChanged(List<ThrottleStatus> statuses) {
-        synchronized (mCallbacks) {
-            for (int i = 0; i < mCallbacks.size(); i++) {
-                mCallbacks.get(i).onThrottleStatusChanged(statuses);
-            }
-        }
-    }
-
-    private static void loge(String s) {
-        Rlog.e(TAG, s);
-    }
-
-    /**
-     * Reports changes to apn throttle statuses.
-     *
-     * Note: All statuses are sent when first registered.
-     *
-     * @param callback status changes callback
-     */
-    public void registerForThrottleStatusChanges(DataThrottler.Callback callback) {
-        synchronized (mCallbacks) {
-            //Only add if it's not there already
-            if (!mCallbacks.contains(callback)) {
-                //Report everything the first time
-                List<ThrottleStatus> throttleStatuses =
-                        new ArrayList<>(mThrottleStatus.values());
-                callback.onThrottleStatusChanged(throttleStatuses);
-                mCallbacks.add(callback);
-            }
-        }
-    }
-
-    /**
-     * Unregister the callback
-     * @param callback the callback to unregister
-     */
-    public void unregisterForThrottleStatusChanges(DataThrottler.Callback callback) {
-        synchronized (mCallbacks) {
-            mCallbacks.remove(callback);
-        }
-    }
-
-    /**
-     * Callback for when throttle statuses change
-     */
-    public interface Callback {
-        /**
-         * Called whenever the throttle status of an APN has changed.
-         *
-         * Note: Called with all statuses when first registered.
-         *
-         * @param throttleStatuses the status changes
-         */
-        void onThrottleStatusChanged(List<ThrottleStatus> throttleStatuses);
-    }
-}
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcController.java b/src/java/com/android/internal/telephony/dataconnection/DcController.java
deleted file mode 100644
index c34157e..0000000
--- a/src/java/com/android/internal/telephony/dataconnection/DcController.java
+++ /dev/null
@@ -1,487 +0,0 @@
-/*
- * Copyright (C) 2013 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.dataconnection;
-
-import android.hardware.radio.V1_4.DataConnActiveStatus;
-import android.net.LinkAddress;
-import android.os.AsyncResult;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RegistrantList;
-import android.telephony.AccessNetworkConstants;
-import android.telephony.CarrierConfigManager;
-import android.telephony.DataFailCause;
-import android.telephony.data.ApnSetting;
-import android.telephony.data.DataCallResponse;
-import android.telephony.data.TrafficDescriptor;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.DctConstants;
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.dataconnection.DataConnection.UpdateLinkPropertyResult;
-import com.android.internal.telephony.util.TelephonyUtils;
-import com.android.net.module.util.LinkPropertiesUtils;
-import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult;
-import com.android.net.module.util.NetUtils;
-import com.android.telephony.Rlog;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Data Connection Controller which is a package visible class and controls
- * multiple data connections. For instance listening for unsolicited messages
- * and then demultiplexing them to the appropriate DC.
- */
-public class DcController extends Handler {
-    private static final boolean DBG = true;
-    private static final boolean VDBG = false;
-
-    private final Phone mPhone;
-    private final DcTracker mDct;
-    private final String mTag;
-    private final DataServiceManager mDataServiceManager;
-    private final DcTesterDeactivateAll mDcTesterDeactivateAll;
-
-    // package as its used by Testing code
-    // @GuardedBy("mDcListAll")
-    final ArrayList<DataConnection> mDcListAll = new ArrayList<>();
-    // @GuardedBy("mDcListAll")
-    private final HashMap<Integer, DataConnection> mDcListActiveByCid = new HashMap<>();
-    // @GuardedBy("mTrafficDescriptorsByCid")
-    private final HashMap<Integer, List<TrafficDescriptor>> mTrafficDescriptorsByCid =
-            new HashMap<>();
-
-    /**
-     * Aggregated physical link status from all data connections. This reflects the device's RRC
-     * connection state.
-     * If {@link CarrierConfigManager#KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL} is true,
-     * then This reflects "internet data connection" instead of RRC state.
-     */
-    private @DataCallResponse.LinkStatus int mPhysicalLinkStatus =
-            DataCallResponse.LINK_STATUS_UNKNOWN;
-
-    private RegistrantList mPhysicalLinkStatusChangedRegistrants = new RegistrantList();
-
-    /**
-     * Constructor.
-     *
-     * @param name to be used for the Controller
-     * @param phone the phone associated with Dcc and Dct
-     * @param dct the DataConnectionTracker associated with Dcc
-     * @param dataServiceManager the data service manager that manages data services
-     * @param looper looper for this handler
-     */
-    private DcController(String name, Phone phone, DcTracker dct,
-                         DataServiceManager dataServiceManager, Looper looper) {
-        super(looper);
-        mPhone = phone;
-        mDct = dct;
-        mTag = name;
-        mDataServiceManager = dataServiceManager;
-
-        mDcTesterDeactivateAll = (TelephonyUtils.IS_DEBUGGABLE)
-                ? new DcTesterDeactivateAll(mPhone, DcController.this, this)
-                : null;
-        mDataServiceManager.registerForDataCallListChanged(this,
-                DataConnection.EVENT_DATA_STATE_CHANGED);
-    }
-
-    public static DcController makeDcc(Phone phone, DcTracker dct,
-                                       DataServiceManager dataServiceManager, Looper looper,
-                                       String tagSuffix) {
-        return new DcController("Dcc" + tagSuffix, phone, dct, dataServiceManager, looper);
-    }
-
-    void addDc(DataConnection dc) {
-        synchronized (mDcListAll) {
-            mDcListAll.add(dc);
-        }
-    }
-
-    void removeDc(DataConnection dc) {
-        synchronized (mDcListAll) {
-            mDcListActiveByCid.remove(dc.mCid);
-            mDcListAll.remove(dc);
-        }
-        synchronized (mTrafficDescriptorsByCid) {
-            mTrafficDescriptorsByCid.remove(dc.mCid);
-        }
-    }
-
-    public void addActiveDcByCid(DataConnection dc) {
-        if (DBG && dc.mCid < 0) {
-            log("addActiveDcByCid dc.mCid < 0 dc=" + dc);
-        }
-        synchronized (mDcListAll) {
-            mDcListActiveByCid.put(dc.mCid, dc);
-        }
-        updateTrafficDescriptorsForCid(dc.mCid, dc.getTrafficDescriptors());
-    }
-
-    DataConnection getActiveDcByCid(int cid) {
-        synchronized (mDcListAll) {
-            return mDcListActiveByCid.get(cid);
-        }
-    }
-
-    void removeActiveDcByCid(DataConnection dc) {
-        synchronized (mDcListAll) {
-            DataConnection removedDc = mDcListActiveByCid.remove(dc.mCid);
-            if (DBG && removedDc == null) {
-                log("removeActiveDcByCid removedDc=null dc=" + dc);
-            }
-        }
-        synchronized (mTrafficDescriptorsByCid) {
-            mTrafficDescriptorsByCid.remove(dc.mCid);
-        }
-    }
-
-    boolean isDefaultDataActive() {
-        synchronized (mDcListAll) {
-            return mDcListActiveByCid.values().stream()
-                    .anyMatch(dc -> dc.getApnContexts().stream()
-                            .anyMatch(apn -> apn.getApnTypeBitmask() == ApnSetting.TYPE_DEFAULT));
-        }
-    }
-
-    List<TrafficDescriptor> getTrafficDescriptorsForCid(int cid) {
-        synchronized (mTrafficDescriptorsByCid) {
-            return mTrafficDescriptorsByCid.get(cid);
-        }
-    }
-
-    void updateTrafficDescriptorsForCid(int cid, List<TrafficDescriptor> tds) {
-        synchronized (mTrafficDescriptorsByCid) {
-            mTrafficDescriptorsByCid.put(cid, tds);
-        }
-    }
-
-    @Override
-    public void handleMessage(Message msg) {
-        AsyncResult ar;
-
-        switch (msg.what) {
-            case DataConnection.EVENT_DATA_STATE_CHANGED:
-                ar = (AsyncResult) msg.obj;
-                if (ar.exception == null) {
-                    onDataStateChanged((ArrayList<DataCallResponse>) ar.result);
-                } else {
-                    log("EVENT_DATA_STATE_CHANGED: exception; likely radio not available, ignore");
-                }
-                break;
-            default:
-                loge("Unexpected event " + msg);
-                break;
-        }
-    }
-
-    /**
-     * Process the new list of "known" Data Calls
-     * @param dcsList as sent by RIL_UNSOL_DATA_CALL_LIST_CHANGED
-     */
-    private void onDataStateChanged(ArrayList<DataCallResponse> dcsList) {
-        final HashMap<Integer, DataConnection> dcListActiveByCid;
-        synchronized (mDcListAll) {
-            dcListActiveByCid = new HashMap<>(mDcListActiveByCid);
-        }
-
-        if (DBG) {
-            log("onDataStateChanged: dcsList=" + dcsList
-                    + " dcListActiveByCid=" + dcListActiveByCid);
-        }
-
-        // Create hashmap of cid to DataCallResponse
-        HashMap<Integer, DataCallResponse> dataCallResponseListByCid = new HashMap<>();
-        for (DataCallResponse dcs : dcsList) {
-            dataCallResponseListByCid.put(dcs.getId(), dcs);
-        }
-
-        // Add a DC that is active but not in the dcsList to the list of DC's to retry
-        ArrayList<DataConnection> dcsToRetry = new ArrayList<>();
-        for (DataConnection dc : dcListActiveByCid.values()) {
-            DataCallResponse response = dataCallResponseListByCid.get(dc.mCid);
-            if (response == null) {
-                if (DBG) log("onDataStateChanged: add to retry dc=" + dc);
-                dcsToRetry.add(dc);
-            } else {
-                List<TrafficDescriptor> oldTds = getTrafficDescriptorsForCid(dc.mCid);
-                List<TrafficDescriptor> newTds = response.getTrafficDescriptors();
-                if (!oldTds.equals(newTds)) {
-                    if (DBG) {
-                        log("onDataStateChanged: add to retry due to TD changed dc=" + dc
-                                + ", oldTds=" + oldTds + ", newTds=" + newTds);
-                    }
-                    updateTrafficDescriptorsForCid(dc.mCid, newTds);
-                    dcsToRetry.add(dc);
-                }
-            }
-        }
-        if (DBG) log("onDataStateChanged: dcsToRetry=" + dcsToRetry);
-
-        // Find which connections have changed state and send a notification or cleanup
-        // and any that are in active need to be retried.
-        ArrayList<ApnContext> apnsToCleanup = new ArrayList<ApnContext>();
-
-        boolean isAnyDataCallDormant = false;
-        boolean isAnyDataCallActive = false;
-        boolean isInternetDataCallActive = false;
-
-        for (DataCallResponse newState : dcsList) {
-
-            DataConnection dc = dcListActiveByCid.get(newState.getId());
-            if (dc == null) {
-                // UNSOL_DATA_CALL_LIST_CHANGED arrived before SETUP_DATA_CALL completed.
-                loge("onDataStateChanged: no associated DC yet, ignore");
-                continue;
-            }
-
-            List<ApnContext> apnContexts = dc.getApnContexts();
-            if (apnContexts.size() == 0) {
-                if (DBG) loge("onDataStateChanged: no connected apns, ignore");
-            } else {
-                // Determine if the connection/apnContext should be cleaned up
-                // or just a notification should be sent out.
-                if (DBG) {
-                    log("onDataStateChanged: Found ConnId=" + newState.getId()
-                            + " newState=" + newState.toString());
-                }
-                if (apnContexts.stream().anyMatch(
-                        i -> ApnSetting.TYPE_DEFAULT_STRING.equals(i.getApnType()))
-                        && newState.getLinkStatus() == DataConnActiveStatus.ACTIVE) {
-                    isInternetDataCallActive = true;
-                }
-                if (newState.getLinkStatus() == DataConnActiveStatus.INACTIVE) {
-                    if (mDct.isCleanupRequired.get()) {
-                        apnsToCleanup.addAll(apnContexts);
-                        mDct.isCleanupRequired.set(false);
-                    } else {
-                        int failCause = DataFailCause.getFailCause(newState.getCause());
-                        if (DataFailCause.isRadioRestartFailure(mPhone.getContext(), failCause,
-                                    mPhone.getSubId())) {
-                            if (DBG) {
-                                log("onDataStateChanged: X restart radio, failCause="
-                                        + failCause);
-                            }
-                            mDct.sendRestartRadio();
-                        } else if (mDct.isPermanentFailure(failCause)) {
-                            if (DBG) {
-                                log("onDataStateChanged: inactive, add to cleanup list. "
-                                        + "failCause=" + failCause);
-                            }
-                            apnsToCleanup.addAll(apnContexts);
-                        } else {
-                            if (DBG) {
-                                log("onDataStateChanged: inactive, add to retry list. "
-                                        + "failCause=" + failCause);
-                            }
-                            dcsToRetry.add(dc);
-                        }
-                    }
-                } else {
-                    // Update the pdu session id
-                    dc.setPduSessionId(newState.getPduSessionId());
-
-                    dc.updatePcscfAddr(newState);
-
-                    // Its active so update the DataConnections link properties
-                    UpdateLinkPropertyResult result = dc.updateLinkProperty(newState);
-                    dc.updateResponseFields(newState);
-                    if (result.oldLp.equals(result.newLp)) {
-                        if (DBG) log("onDataStateChanged: no change");
-                    } else {
-                        if (LinkPropertiesUtils.isIdenticalInterfaceName(
-                                result.oldLp, result.newLp)) {
-                            if (!LinkPropertiesUtils.isIdenticalDnses(
-                                    result.oldLp, result.newLp)
-                                    || !LinkPropertiesUtils.isIdenticalRoutes(
-                                            result.oldLp, result.newLp)
-                                    || !LinkPropertiesUtils.isIdenticalHttpProxy(
-                                            result.oldLp, result.newLp)
-                                    || !LinkPropertiesUtils.isIdenticalAddresses(
-                                            result.oldLp, result.newLp)) {
-                                // If the same address type was removed and
-                                // added we need to cleanup
-                                CompareOrUpdateResult<Integer, LinkAddress> car
-                                    = new CompareOrUpdateResult(
-                                  result.oldLp != null ?
-                                    result.oldLp.getLinkAddresses() : null,
-                                  result.newLp != null ?
-                                    result.newLp.getLinkAddresses() : null,
-                                  (la) -> Objects.hash(((LinkAddress)la).getAddress(),
-                                                       ((LinkAddress)la).getPrefixLength(),
-                                                       ((LinkAddress)la).getScope()));
-                                if (DBG) {
-                                    log("onDataStateChanged: oldLp=" + result.oldLp
-                                            + " newLp=" + result.newLp + " car=" + car);
-                                }
-                                boolean needToClean = false;
-                                for (LinkAddress added : car.added) {
-                                    for (LinkAddress removed : car.removed) {
-                                        if (NetUtils.addressTypeMatches(
-                                                removed.getAddress(),
-                                                added.getAddress())) {
-                                            needToClean = true;
-                                            break;
-                                        }
-                                    }
-                                }
-                                if (needToClean) {
-                                    if (DBG) {
-                                        log("onDataStateChanged: addr change,"
-                                                + " cleanup apns=" + apnContexts
-                                                + " oldLp=" + result.oldLp
-                                                + " newLp=" + result.newLp);
-                                    }
-                                    apnsToCleanup.addAll(apnContexts);
-                                }
-                            } else {
-                                if (DBG) {
-                                    log("onDataStateChanged: no changes");
-                                }
-                            }
-                        } else {
-                            apnsToCleanup.addAll(apnContexts);
-                            if (DBG) {
-                                log("onDataStateChanged: interface change, cleanup apns="
-                                        + apnContexts);
-                            }
-                        }
-                    }
-                }
-            }
-
-            if (newState.getLinkStatus() == DataConnActiveStatus.ACTIVE) {
-                isAnyDataCallActive = true;
-            }
-            if (newState.getLinkStatus() == DataConnActiveStatus.DORMANT) {
-                isAnyDataCallDormant = true;
-            }
-        }
-
-        if (mDataServiceManager.getTransportType()
-                == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
-            boolean isPhysicalLinkStatusFocusingOnInternetData =
-                    mDct.getLteEndcUsingUserDataForIdleDetection();
-            int physicalLinkStatus =
-                    (isPhysicalLinkStatusFocusingOnInternetData
-                            ? isInternetDataCallActive : isAnyDataCallActive)
-                            ? DataCallResponse.LINK_STATUS_ACTIVE
-                            : DataCallResponse.LINK_STATUS_DORMANT;
-            if (mPhysicalLinkStatus != physicalLinkStatus) {
-                mPhysicalLinkStatus = physicalLinkStatus;
-                mPhysicalLinkStatusChangedRegistrants.notifyResult(mPhysicalLinkStatus);
-            }
-            if (isAnyDataCallDormant && !isAnyDataCallActive) {
-                // There is no way to indicate link activity per APN right now. So
-                // Link Activity will be considered dormant only when all data calls
-                // are dormant.
-                // If a single data call is in dormant state and none of the data
-                // calls are active broadcast overall link status as dormant.
-                if (DBG) {
-                    log("onDataStateChanged: Data activity DORMANT. stopNetStatePoll");
-                }
-                mDct.sendStopNetStatPoll(DctConstants.Activity.DORMANT);
-            } else {
-                if (DBG) {
-                    log("onDataStateChanged: Data Activity updated to NONE. "
-                            + "isAnyDataCallActive = " + isAnyDataCallActive
-                            + " isAnyDataCallDormant = " + isAnyDataCallDormant);
-                }
-                if (isAnyDataCallActive) {
-                    mDct.sendStartNetStatPoll(DctConstants.Activity.NONE);
-                }
-            }
-        }
-
-        if (DBG) {
-            log("onDataStateChanged: dcsToRetry=" + dcsToRetry
-                    + " apnsToCleanup=" + apnsToCleanup);
-        }
-
-        // Cleanup connections that have changed
-        for (ApnContext apnContext : apnsToCleanup) {
-            mDct.cleanUpConnection(apnContext);
-        }
-
-        // Retry connections that have disappeared
-        for (DataConnection dc : dcsToRetry) {
-            if (DBG) log("onDataStateChanged: send EVENT_LOST_CONNECTION dc.mTag=" + dc.mTag);
-            dc.sendMessage(DataConnection.EVENT_LOST_CONNECTION, dc.mTag);
-        }
-
-        if (VDBG) log("onDataStateChanged: X");
-    }
-
-    /**
-     * Register for physical link status (i.e. RRC state) changed event.
-     * if {@link CarrierConfigManager#KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL} is true,
-     * then physical link status is focusing on "internet data connection" instead of RRC state.
-     * @param h The handler
-     * @param what The event
-     */
-    @VisibleForTesting
-    public void registerForPhysicalLinkStatusChanged(Handler h, int what) {
-        mPhysicalLinkStatusChangedRegistrants.addUnique(h, what, null);
-    }
-
-    /**
-     * Unregister from physical link status (i.e. RRC state) changed event.
-     *
-     * @param h The previously registered handler
-     */
-    void unregisterForPhysicalLinkStatusChanged(Handler h) {
-        mPhysicalLinkStatusChangedRegistrants.remove(h);
-    }
-
-    private void log(String s) {
-        Rlog.d(mTag, s);
-    }
-
-    private void loge(String s) {
-        Rlog.e(mTag, s);
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        synchronized (mDcListAll) {
-            sb.append("mDcListAll=").append(mDcListAll)
-                    .append(" mDcListActiveByCid=").append(mDcListActiveByCid);
-        }
-        synchronized (mTrafficDescriptorsByCid) {
-            sb.append("mTrafficDescriptorsByCid=").append(mTrafficDescriptorsByCid);
-        }
-        return sb.toString();
-    }
-
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println(" mPhone=" + mPhone);
-        synchronized (mDcListAll) {
-            pw.println(" mDcListAll=" + mDcListAll);
-            pw.println(" mDcListActiveByCid=" + mDcListActiveByCid);
-        }
-        synchronized (mTrafficDescriptorsByCid) {
-            pw.println(" mTrafficDescriptorsByCid=" + mTrafficDescriptorsByCid);
-        }
-    }
-}
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcFailBringUp.java b/src/java/com/android/internal/telephony/dataconnection/DcFailBringUp.java
deleted file mode 100644
index 3cdd209..0000000
--- a/src/java/com/android/internal/telephony/dataconnection/DcFailBringUp.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2013 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.dataconnection;
-
-import android.content.Intent;
-import android.telephony.Annotation.DataFailureCause;
-import android.telephony.DataFailCause;
-
-import com.android.telephony.Rlog;
-
-/**
- * A package visible class for supporting testing failing bringUp commands. This
- * saves the parameters from a action_fail_bringup intent. See
- * {@link DataConnection#doOnConnect} and {@see DcTesterFailBringUpAll} for more info.
- */
-public class DcFailBringUp {
-    private static final String LOG_TAG = "DcFailBringUp";
-    private static final boolean DBG = true;
-
-    static final String INTENT_BASE = DataConnection.class.getPackage().getName();
-
-    static final String ACTION_FAIL_BRINGUP = "action_fail_bringup";
-
-    // counter with its --ei option name and default value
-    static final String COUNTER = "counter";
-    static final int DEFAULT_COUNTER = 2;
-    int mCounter;
-
-    // failCause with its --ei option name and default value
-    static final String FAIL_CAUSE = "fail_cause";
-    static final int DEFAULT_FAIL_CAUSE = DataFailCause.ERROR_UNSPECIFIED;
-    @DataFailureCause
-    int mFailCause;
-
-    // suggestedRetryTime with its --ei option name and default value
-    static final String SUGGESTED_RETRY_TIME = "suggested_retry_time";
-    static final long DEFAULT_SUGGESTED_RETRY_TIME = -1;
-    long mSuggestedRetryTime;
-
-    // Get the Extra Intent parameters
-    void saveParameters(Intent intent, String s) {
-        if (DBG) log(s + ".saveParameters: action=" + intent.getAction());
-        mCounter = intent.getIntExtra(COUNTER, DEFAULT_COUNTER);
-        mFailCause = DataFailCause.getFailCause(
-                intent.getIntExtra(FAIL_CAUSE, DEFAULT_FAIL_CAUSE));
-        mSuggestedRetryTime =
-                intent.getLongExtra(SUGGESTED_RETRY_TIME, DEFAULT_SUGGESTED_RETRY_TIME);
-        if (DBG) {
-            log(s + ".saveParameters: " + this);
-        }
-    }
-
-    public void saveParameters(int counter, @DataFailureCause int failCause,
-                               long suggestedRetryTime) {
-        mCounter = counter;
-        mFailCause = DataFailCause.getFailCause(failCause);
-        mSuggestedRetryTime = suggestedRetryTime;
-    }
-
-    @Override
-    public String toString() {
-        return "{mCounter=" + mCounter +
-                " mFailCause=" + mFailCause +
-                " mSuggestedRetryTime=" + mSuggestedRetryTime + "}";
-
-    }
-
-    private static void log(String s) {
-        Rlog.d(LOG_TAG, s);
-    }
-}
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcNetworkAgent.java b/src/java/com/android/internal/telephony/dataconnection/DcNetworkAgent.java
deleted file mode 100644
index c6def34..0000000
--- a/src/java/com/android/internal/telephony/dataconnection/DcNetworkAgent.java
+++ /dev/null
@@ -1,617 +0,0 @@
-/*
- * 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.dataconnection;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.KeepalivePacketData;
-import android.net.LinkProperties;
-import android.net.NattKeepalivePacketData;
-import android.net.NetworkAgent;
-import android.net.NetworkAgentConfig;
-import android.net.NetworkCapabilities;
-import android.net.NetworkProvider;
-import android.net.QosFilter;
-import android.net.QosSessionAttributes;
-import android.net.SocketKeepalive;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Message;
-import android.telephony.AccessNetworkConstants;
-import android.telephony.AccessNetworkConstants.TransportType;
-import android.telephony.Annotation.NetworkType;
-import android.telephony.AnomalyReporter;
-import android.telephony.NetworkRegistrationInfo;
-import android.telephony.ServiceState;
-import android.telephony.TelephonyManager;
-import android.telephony.data.QosBearerSession;
-import android.util.LocalLog;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.DctConstants;
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.RILConstants;
-import com.android.internal.telephony.SlidingWindowEventCounter;
-import com.android.internal.telephony.data.KeepaliveStatus;
-import com.android.internal.telephony.data.NotifyQosSessionInterface;
-import com.android.internal.telephony.data.QosCallbackTracker;
-import com.android.internal.telephony.metrics.TelephonyMetrics;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.telephony.Rlog;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.net.InetAddress;
-import java.time.Duration;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.UUID;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-/**
- * This class represents a network agent which is communication channel between
- * {@link DataConnection} and {@link com.android.server.ConnectivityService}. The agent is
- * created when data connection enters {@link DataConnection.DcActiveState} until it exits that
- * state.
- *
- * Note that in IWLAN handover scenario, this agent could be transferred to the new
- * {@link DataConnection} so for a short window of time this object might be accessed by two
- * different {@link DataConnection}. Thus each method in this class needs to be synchronized.
- */
-public class DcNetworkAgent extends NetworkAgent implements NotifyQosSessionInterface {
-    private final String mTag;
-
-    private final int mId;
-
-    private final Phone mPhone;
-
-    private final Handler mHandler;
-
-    private int mTransportType;
-
-    private NetworkCapabilities mNetworkCapabilities;
-
-    public final DcKeepaliveTracker keepaliveTracker = new DcKeepaliveTracker();
-
-    private final QosCallbackTracker mQosCallbackTracker;
-
-    private final Executor mQosCallbackExecutor = Executors.newSingleThreadExecutor();
-
-    private DataConnection mDataConnection;
-
-    private final LocalLog mNetCapsLocalLog = new LocalLog(32);
-
-    // For interface duplicate detection. Key is the net id, value is the interface name in string.
-    private static Map<Integer, String> sInterfaceNames = new ConcurrentHashMap<>();
-
-    private static final long NETWORK_UNWANTED_ANOMALY_WINDOW_MS = TimeUnit.MINUTES.toMillis(5);
-    private static final int NETWORK_UNWANTED_ANOMALY_NUM_OCCURRENCES =  12;
-
-    private static final int EVENT_UNWANTED_TIMEOUT = 1;
-
-    @VisibleForTesting
-    public DcNetworkAgent(DataConnection dc, Phone phone, int score, NetworkAgentConfig config,
-            NetworkProvider networkProvider, int transportType) {
-        super(phone.getContext(), dc.getHandler().getLooper(), "DcNetworkAgent",
-                dc.getNetworkCapabilities(), dc.getLinkProperties(), score, config,
-                networkProvider);
-        register();
-        mId = getNetwork().getNetId();
-        mTag = "DcNetworkAgent" + "-" + mId;
-        mPhone = phone;
-        mHandler = new Handler(dc.getHandler().getLooper()) {
-            @Override
-            public void handleMessage(Message msg) {
-                if (msg.what == EVENT_UNWANTED_TIMEOUT) {
-                    loge("onNetworkUnwanted timed out. Perform silent de-register.");
-                    logd("Unregister from connectivity service. " + sInterfaceNames.get(mId)
-                            + " removed.");
-                    sInterfaceNames.remove(mId);
-                    DcNetworkAgent.this.unregister();
-                }
-            }
-        };
-        mNetworkCapabilities = dc.getNetworkCapabilities();
-        mTransportType = transportType;
-        mDataConnection = dc;
-        if (dc.getLinkProperties() != null) {
-            checkDuplicateInterface(mId, dc.getLinkProperties().getInterfaceName());
-            logd("created for data connection " + dc.getName() + ", "
-                    + dc.getLinkProperties().getInterfaceName());
-        } else {
-            loge("The connection does not have a valid link properties.");
-        }
-        mQosCallbackTracker = new QosCallbackTracker(this, mPhone);
-    }
-
-    private @NetworkType int getNetworkType() {
-        ServiceState ss = mPhone.getServiceState();
-        int networkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
-
-        NetworkRegistrationInfo nri = ss.getNetworkRegistrationInfo(
-                NetworkRegistrationInfo.DOMAIN_PS, mTransportType);
-        if (nri != null) {
-            networkType = nri.getAccessNetworkTechnology();
-        }
-
-        return networkType;
-    }
-
-    private void checkDuplicateInterface(int netId, @Nullable String interfaceName) {
-        for (Map.Entry<Integer, String> entry: sInterfaceNames.entrySet()) {
-            if (Objects.equals(interfaceName, entry.getValue())) {
-                String message = "Duplicate interface " + interfaceName
-                        + " is detected. DcNetworkAgent-" + entry.getKey()
-                        + " already used this interface name.";
-                loge(message);
-                // Using fixed UUID to avoid duplicate bugreport notification
-                AnomalyReporter.reportAnomaly(
-                        UUID.fromString("02f3d3f6-4613-4415-b6cb-8d92c8a938a6"),
-                        message, mPhone.getCarrierId());
-                return;
-            }
-        }
-        sInterfaceNames.put(netId, interfaceName);
-    }
-
-    /**
-     * @return The tag
-     */
-    String getTag() {
-        return mTag;
-    }
-
-    /**
-     * Set the data connection that owns this network agent.
-     *
-     * @param dc Data connection owning this network agent.
-     * @param transportType Transport that this data connection is on.
-     */
-    public synchronized void acquireOwnership(@NonNull DataConnection dc,
-                                              @TransportType int transportType) {
-        mDataConnection = dc;
-        mTransportType = transportType;
-        logd(dc.getName() + " acquired the ownership of this agent.");
-    }
-
-    /**
-     * Release the ownership of network agent.
-     */
-    public synchronized void releaseOwnership(DataConnection dc) {
-        if (mDataConnection == null) {
-            loge("releaseOwnership called on no-owner DcNetworkAgent!");
-            return;
-        } else if (mDataConnection != dc) {
-            loge("releaseOwnership: This agent belongs to "
-                    + mDataConnection.getName() + ", ignored the request from " + dc.getName());
-            return;
-        }
-        logd("Data connection " + mDataConnection.getName() + " released the ownership.");
-        mDataConnection = null;
-    }
-
-    /**
-     * @return The data connection that owns this agent
-     */
-    public synchronized DataConnection getDataConnection() {
-        return mDataConnection;
-    }
-
-    private static final SlidingWindowEventCounter sNetworkUnwantedCounter =
-            new SlidingWindowEventCounter(NETWORK_UNWANTED_ANOMALY_WINDOW_MS,
-                    NETWORK_UNWANTED_ANOMALY_NUM_OCCURRENCES);
-
-    @Override
-    public synchronized void onNetworkUnwanted() {
-        mHandler.sendEmptyMessageDelayed(EVENT_UNWANTED_TIMEOUT, TimeUnit.SECONDS.toMillis(30));
-        trackNetworkUnwanted();
-        if (mDataConnection == null) {
-            loge("onNetworkUnwanted found called on no-owner DcNetworkAgent!");
-            return;
-        }
-
-        logd("onNetworkUnwanted called. Now tear down the data connection "
-                + mDataConnection.getName());
-        mDataConnection.tearDownAll(Phone.REASON_RELEASED_BY_CONNECTIVITY_SERVICE,
-                DcTracker.RELEASE_TYPE_DETACH, null);
-    }
-
-    /**
-     * There have been several bugs where a RECONNECT loop kicks off where a DataConnection
-     * connects to the Network, ConnectivityService indicates that the Network is unwanted,
-     * and then the DataConnection reconnects.  By the time we get the bug report it's too late
-     * because there have already been hundreds of RECONNECTS.  This is meant to capture the issue
-     * when it first starts.
-     *
-     * The unwanted counter is configured to only take an anomaly report in extreme cases.
-     * This is to avoid having the anomaly message show up on several devices.
-     *
-     * This is directly related to b/175845538.  But, there have been several other occurrences of
-     * this issue.
-     */
-    private void trackNetworkUnwanted() {
-        if (sNetworkUnwantedCounter.addOccurrence()) {
-            AnomalyReporter.reportAnomaly(
-                    UUID.fromString("3f578b5c-64e9-11eb-ae93-0242ac130002"),
-                    "Network Unwanted called 12 times in 5 minutes.", mPhone.getCarrierId());
-        }
-    }
-
-    @Override
-    public synchronized void onBandwidthUpdateRequested() {
-        if (mDataConnection == null) {
-            loge("onBandwidthUpdateRequested called on no-owner DcNetworkAgent!");
-            return;
-        }
-
-        if (mPhone.getLceStatus() == RILConstants.LCE_ACTIVE     // active LCE service
-                && mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
-            mPhone.mCi.pullLceData(mDataConnection.obtainMessage(
-                    DataConnection.EVENT_BW_REFRESH_RESPONSE));
-        }
-    }
-
-    @Override
-    public synchronized void onValidationStatus(int status, Uri redirectUri) {
-        if (mDataConnection == null) {
-            loge("onValidationStatus called on no-owner DcNetworkAgent!");
-            return;
-        }
-
-        logd("validation status: " + status + " with redirection URL: " + redirectUri);
-        DcTracker dct = mPhone.getDcTracker(mTransportType);
-        if (dct != null) {
-            Message msg = dct.obtainMessage(DctConstants.EVENT_NETWORK_STATUS_CHANGED,
-                    status, mDataConnection.getCid(), redirectUri.toString());
-            msg.sendToTarget();
-        }
-    }
-
-    private synchronized boolean isOwned(DataConnection dc, String reason) {
-        if (mDataConnection == null) {
-            loge(reason + " called on no-owner DcNetworkAgent!");
-            return false;
-        } else if (mDataConnection != dc) {
-            loge(reason + ": This agent belongs to "
-                    + mDataConnection.getName() + ", ignored the request from " + dc.getName());
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Update the legacy sub type (i.e. data network type).
-     *
-     * @param dc The data connection that invokes this method.
-     */
-    public synchronized void updateLegacySubtype(DataConnection dc) {
-        if (!isOwned(dc, "updateLegacySubtype")) return;
-
-        int networkType = getNetworkType();
-        setLegacySubtype(networkType, TelephonyManager.getNetworkTypeName(networkType));
-    }
-
-    /**
-     * Set the network capabilities.
-     *
-     * @param networkCapabilities The network capabilities.
-     * @param dc The data connection that invokes this method.
-     */
-    public synchronized void sendNetworkCapabilities(NetworkCapabilities networkCapabilities,
-                                                     DataConnection dc) {
-        if (!isOwned(dc, "sendNetworkCapabilities")) return;
-
-        if (!networkCapabilities.equals(mNetworkCapabilities)) {
-            String logStr = "Changed from " + mNetworkCapabilities + " to "
-                    + networkCapabilities + ", Data RAT="
-                    + mPhone.getServiceState().getRilDataRadioTechnology()
-                    + ", dc=" + mDataConnection.getName();
-            logd(logStr);
-            mNetCapsLocalLog.log(logStr);
-            if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
-                // only log metrics for DataConnection with NET_CAPABILITY_INTERNET
-                if (mNetworkCapabilities == null
-                        || networkCapabilities.hasCapability(
-                                NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED)
-                        != mNetworkCapabilities.hasCapability(
-                                NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED)) {
-                    TelephonyMetrics.getInstance().writeNetworkCapabilitiesChangedEvent(
-                            mPhone.getPhoneId(), networkCapabilities);
-                }
-            }
-            mNetworkCapabilities = networkCapabilities;
-        }
-        sendNetworkCapabilities(networkCapabilities);
-    }
-
-    /**
-     * Set the link properties
-     *
-     * @param linkProperties The link properties
-     * @param dc The data connection that invokes this method.
-     */
-    public synchronized void sendLinkProperties(@NonNull LinkProperties linkProperties,
-                                                DataConnection dc) {
-        if (!isOwned(dc, "sendLinkProperties")) return;
-
-        sInterfaceNames.put(mId, dc.getLinkProperties().getInterfaceName());
-        sendLinkProperties(linkProperties);
-    }
-
-    /**
-     * Set the network score.
-     *
-     * @param score The network score.
-     * @param dc The data connection that invokes this method.
-     */
-    public synchronized void sendNetworkScore(int score, DataConnection dc) {
-        if (!isOwned(dc, "sendNetworkScore")) return;
-        sendNetworkScore(score);
-    }
-
-    /**
-     * Unregister the network agent from connectivity service.
-     *
-     * @param dc The data connection that invokes this method.
-     */
-    public synchronized void unregister(DataConnection dc) {
-        if (!isOwned(dc, "unregister")) return;
-
-        mHandler.removeMessages(EVENT_UNWANTED_TIMEOUT);
-        logd("Unregister from connectivity service. " + sInterfaceNames.get(mId) + " removed.");
-        sInterfaceNames.remove(mId);
-        super.unregister();
-    }
-
-    @Override
-    public synchronized void onStartSocketKeepalive(int slot, @NonNull Duration interval,
-            @NonNull KeepalivePacketData packet) {
-        if (mDataConnection == null) {
-            loge("onStartSocketKeepalive called on no-owner DcNetworkAgent!");
-            return;
-        }
-
-        if (packet instanceof NattKeepalivePacketData) {
-            mDataConnection.obtainMessage(DataConnection.EVENT_KEEPALIVE_START_REQUEST,
-                    slot, (int) interval.getSeconds(), packet).sendToTarget();
-        } else {
-            sendSocketKeepaliveEvent(slot, SocketKeepalive.ERROR_UNSUPPORTED);
-        }
-    }
-
-    @Override
-    public synchronized void onStopSocketKeepalive(int slot) {
-        if (mDataConnection == null) {
-            loge("onStopSocketKeepalive called on no-owner DcNetworkAgent!");
-            return;
-        }
-
-        mDataConnection.obtainMessage(DataConnection.EVENT_KEEPALIVE_STOP_REQUEST, slot)
-                .sendToTarget();
-    }
-
-    @Override
-    public void onQosCallbackRegistered(final int qosCallbackId, final @NonNull QosFilter filter) {
-        mQosCallbackExecutor.execute(() -> mQosCallbackTracker.addFilter(qosCallbackId,
-              new QosCallbackTracker.IFilter() {
-                  @Override
-                  public boolean matchesLocalAddress(
-                          InetAddress address, int startPort, int endPort) {
-                      return filter.matchesLocalAddress(address, startPort, endPort);
-                  }
-
-                  @Override
-                  public boolean matchesRemoteAddress(
-                          InetAddress address, int startPort, int endPort) {
-                      return filter.matchesRemoteAddress(address, startPort, endPort);
-                  }
-              }));
-    }
-
-    @Override
-    public void onQosCallbackUnregistered(final int qosCallbackId) {
-        mQosCallbackExecutor.execute(() -> mQosCallbackTracker.removeFilter(qosCallbackId));
-    }
-
-    void updateQosBearerSessions(final List<QosBearerSession> qosBearerSessions) {
-        mQosCallbackExecutor.execute(() -> mQosCallbackTracker.updateSessions(qosBearerSessions));
-    }
-
-    @Override
-    public void notifyQosSessionAvailable(final int qosCallbackId, final int sessionId,
-            @NonNull final QosSessionAttributes attributes) {
-        super.sendQosSessionAvailable(qosCallbackId, sessionId, attributes);
-    }
-
-    @Override
-    public void notifyQosSessionLost(final int qosCallbackId,
-            final int sessionId, final int qosSessionType) {
-        super.sendQosSessionLost(qosCallbackId, sessionId, qosSessionType);
-    }
-
-    @Override
-    public String toString() {
-        return "DcNetworkAgent-"
-                + mId
-                + " mDataConnection="
-                + ((mDataConnection != null) ? mDataConnection.getName() : null)
-                + " mTransportType="
-                + AccessNetworkConstants.transportTypeToString(mTransportType)
-                + " " + ((mDataConnection != null) ? mDataConnection.getLinkProperties() : null)
-                + " mNetworkCapabilities=" + mNetworkCapabilities;
-    }
-
-    /**
-     * Dump the state of transport manager
-     *
-     * @param fd File descriptor
-     * @param printWriter Print writer
-     * @param args Arguments
-     */
-    public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
-        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
-        pw.println(toString());
-        pw.increaseIndent();
-        pw.println("Net caps logs:");
-        mNetCapsLocalLog.dump(fd, pw, args);
-        pw.decreaseIndent();
-    }
-
-    /**
-     * Log with debug level
-     *
-     * @param s is string log
-     */
-    private void logd(String s) {
-        Rlog.d(mTag, s);
-    }
-
-    /**
-     * Log with error level
-     *
-     * @param s is string log
-     */
-    private void loge(String s) {
-        Rlog.e(mTag, s);
-    }
-
-    class DcKeepaliveTracker {
-        private class KeepaliveRecord {
-            public int slotId;
-            public int currentStatus;
-
-            KeepaliveRecord(int slotId, int status) {
-                this.slotId = slotId;
-                this.currentStatus = status;
-            }
-        }
-
-        private final SparseArray<KeepaliveRecord> mKeepalives = new SparseArray();
-
-        int getHandleForSlot(int slotId) {
-            for (int i = 0; i < mKeepalives.size(); i++) {
-                KeepaliveRecord kr = mKeepalives.valueAt(i);
-                if (kr.slotId == slotId) return mKeepalives.keyAt(i);
-            }
-            return -1;
-        }
-
-        int keepaliveStatusErrorToPacketKeepaliveError(int error) {
-            switch(error) {
-                case KeepaliveStatus.ERROR_NONE:
-                    return SocketKeepalive.SUCCESS;
-                case KeepaliveStatus.ERROR_UNSUPPORTED:
-                    return SocketKeepalive.ERROR_UNSUPPORTED;
-                case KeepaliveStatus.ERROR_NO_RESOURCES:
-                    return SocketKeepalive.ERROR_INSUFFICIENT_RESOURCES;
-                case KeepaliveStatus.ERROR_UNKNOWN:
-                default:
-                    return SocketKeepalive.ERROR_HARDWARE_ERROR;
-            }
-        }
-
-        void handleKeepaliveStarted(final int slot, KeepaliveStatus ks) {
-            switch (ks.statusCode) {
-                case KeepaliveStatus.STATUS_INACTIVE:
-                    DcNetworkAgent.this.sendSocketKeepaliveEvent(slot,
-                            keepaliveStatusErrorToPacketKeepaliveError(ks.errorCode));
-                    break;
-                case KeepaliveStatus.STATUS_ACTIVE:
-                    DcNetworkAgent.this.sendSocketKeepaliveEvent(
-                            slot, SocketKeepalive.SUCCESS);
-                    // fall through to add record
-                case KeepaliveStatus.STATUS_PENDING:
-                    logd("Adding keepalive handle="
-                            + ks.sessionHandle + " slot = " + slot);
-                    mKeepalives.put(ks.sessionHandle,
-                            new KeepaliveRecord(
-                                    slot, ks.statusCode));
-                    break;
-                default:
-                    logd("Invalid KeepaliveStatus Code: " + ks.statusCode);
-                    break;
-            }
-        }
-
-        void handleKeepaliveStatus(KeepaliveStatus ks) {
-            final KeepaliveRecord kr;
-            kr = mKeepalives.get(ks.sessionHandle);
-
-            if (kr == null) {
-                // If there is no slot for the session handle, we received an event
-                // for a different data connection. This is not an error because the
-                // keepalive session events are broadcast to all listeners.
-                loge("Discarding keepalive event for different data connection:" + ks);
-                return;
-            }
-            // Switch on the current state, to see what we do with the status update
-            switch (kr.currentStatus) {
-                case KeepaliveStatus.STATUS_INACTIVE:
-                    logd("Inactive Keepalive received status!");
-                    DcNetworkAgent.this.sendSocketKeepaliveEvent(
-                            kr.slotId, SocketKeepalive.ERROR_HARDWARE_ERROR);
-                    break;
-                case KeepaliveStatus.STATUS_PENDING:
-                    switch (ks.statusCode) {
-                        case KeepaliveStatus.STATUS_INACTIVE:
-                            DcNetworkAgent.this.sendSocketKeepaliveEvent(kr.slotId,
-                                    keepaliveStatusErrorToPacketKeepaliveError(ks.errorCode));
-                            kr.currentStatus = KeepaliveStatus.STATUS_INACTIVE;
-                            mKeepalives.remove(ks.sessionHandle);
-                            break;
-                        case KeepaliveStatus.STATUS_ACTIVE:
-                            logd("Pending Keepalive received active status!");
-                            kr.currentStatus = KeepaliveStatus.STATUS_ACTIVE;
-                            DcNetworkAgent.this.sendSocketKeepaliveEvent(
-                                    kr.slotId, SocketKeepalive.SUCCESS);
-                            break;
-                        case KeepaliveStatus.STATUS_PENDING:
-                            loge("Invalid unsolicied Keepalive Pending Status!");
-                            break;
-                        default:
-                            loge("Invalid Keepalive Status received, " + ks.statusCode);
-                    }
-                    break;
-                case KeepaliveStatus.STATUS_ACTIVE:
-                    switch (ks.statusCode) {
-                        case KeepaliveStatus.STATUS_INACTIVE:
-                            logd("Keepalive received stopped status!");
-                            DcNetworkAgent.this.sendSocketKeepaliveEvent(
-                                    kr.slotId, SocketKeepalive.SUCCESS);
-
-                            kr.currentStatus = KeepaliveStatus.STATUS_INACTIVE;
-                            mKeepalives.remove(ks.sessionHandle);
-                            break;
-                        case KeepaliveStatus.STATUS_PENDING:
-                        case KeepaliveStatus.STATUS_ACTIVE:
-                            loge("Active Keepalive received invalid status!");
-                            break;
-                        default:
-                            loge("Invalid Keepalive Status received, " + ks.statusCode);
-                    }
-                    break;
-                default:
-                    loge("Invalid Keepalive Status received, " + kr.currentStatus);
-            }
-        }
-    }
-}
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcRequest.java b/src/java/com/android/internal/telephony/dataconnection/DcRequest.java
deleted file mode 100644
index da775e8..0000000
--- a/src/java/com/android/internal/telephony/dataconnection/DcRequest.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2006 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.dataconnection;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.NetworkRequest;
-import android.net.NetworkSpecifier;
-import android.net.TelephonyNetworkSpecifier;
-import android.telephony.Annotation.ApnType;
-
-import com.android.telephony.Rlog;
-
-/**
- * Wraps cellular network requests to configured apn types.
- */
-public class DcRequest implements Comparable<DcRequest> {
-    private static final String LOG_TAG = "DcRequest";
-
-    @NonNull
-    public final NetworkRequest networkRequest;
-    public final int priority;
-    public final @ApnType int apnType;
-
-    private DcRequest(@NonNull final NetworkRequest nr, @ApnType final int type,
-            int apnPriority) {
-        networkRequest = nr;
-        priority = apnPriority;
-        apnType = type;
-    }
-
-    /**
-     * Create a DcRequest based off of the network request.  If the network request is not cellular,
-     * then null is returned and a warning is generated.
-     * @param networkRequest sets the type of dc request
-     * @param apnConfigTypeRepository apn config types to match on the network request
-     * @return corresponding DcRequest
-     *
-     */
-    @Nullable
-    public static DcRequest create(@NonNull final NetworkRequest networkRequest,
-            @NonNull final ApnConfigTypeRepository apnConfigTypeRepository) {
-        final int apnType = ApnContext.getApnTypeFromNetworkRequest(networkRequest);
-        final ApnConfigType apnConfigType = apnConfigTypeRepository.getByType(apnType);
-        if (apnConfigType == null) {
-            Rlog.d(LOG_TAG, "Non cellular request ignored: " + networkRequest.toString());
-            checkForAnomalousNetworkRequest(networkRequest);
-            return null;
-        } else {
-            Rlog.d(LOG_TAG, "Cellular request confirmed: " + networkRequest.toString());
-            return new DcRequest(networkRequest, apnType, apnConfigType.getPriority());
-        }
-    }
-
-    private static void checkForAnomalousNetworkRequest(NetworkRequest networkRequest) {
-        NetworkSpecifier specifier = networkRequest.getNetworkSpecifier();
-        if (specifier != null) {
-            if (specifier instanceof TelephonyNetworkSpecifier) {
-                reportAnomalousNetworkRequest(networkRequest);
-            }
-        }
-    }
-
-    private static void reportAnomalousNetworkRequest(NetworkRequest networkRequest) {
-        //TODO: Report anomaly if this happens
-        Rlog.w(LOG_TAG, "A TelephonyNetworkSpecifier for a non-cellular request is invalid: "
-                + networkRequest.toString());
-
-    }
-
-    public String toString() {
-        return networkRequest.toString() + ", priority=" + priority + ", apnType=" + apnType;
-    }
-
-    public int hashCode() {
-        return networkRequest.hashCode();
-    }
-
-    public boolean equals(Object o) {
-        if (o instanceof DcRequest) {
-            return networkRequest.equals(((DcRequest)o).networkRequest);
-        }
-        return false;
-    }
-
-    public int compareTo(DcRequest o) {
-        return o.priority - priority;
-    }
-}
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcTesterDeactivateAll.java b/src/java/com/android/internal/telephony/dataconnection/DcTesterDeactivateAll.java
deleted file mode 100644
index 11a0ae6..0000000
--- a/src/java/com/android/internal/telephony/dataconnection/DcTesterDeactivateAll.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2013 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.dataconnection;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Handler;
-
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.util.TelephonyUtils;
-import com.android.telephony.Rlog;
-
-/**
- * To bring down all DC's send the following intent:
- *
- * adb shell am broadcast -a com.android.internal.telephony.dataconnection.action_deactivate_all
- */
-public class DcTesterDeactivateAll {
-    private static final String LOG_TAG = "DcTesterDeacativateAll";
-    private static final boolean DBG = true;
-
-    private Phone mPhone;
-    private DcController mDcc;
-
-    public static String sActionDcTesterDeactivateAll =
-            "com.android.internal.telephony.dataconnection.action_deactivate_all";
-
-
-    // The static intent receiver one for all instances and we assume this
-    // is running on the same thread as Dcc.
-    protected BroadcastReceiver sIntentReceiver = new BroadcastReceiver() {
-            @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (DBG) log("sIntentReceiver.onReceive: action=" + action);
-            if (action.equals(sActionDcTesterDeactivateAll)
-                    || action.equals(mPhone.getActionDetached())) {
-                log("Send DEACTIVATE to all Dcc's");
-                if (mDcc != null) {
-                    for (DataConnection dc : mDcc.mDcListAll) {
-                        dc.tearDownNow();
-                    }
-                } else {
-                    if (DBG) log("onReceive: mDcc is null, ignoring");
-                }
-            } else {
-                if (DBG) log("onReceive: unknown action=" + action);
-            }
-        }
-    };
-
-    DcTesterDeactivateAll(Phone phone, DcController dcc, Handler handler) {
-        mPhone = phone;
-        mDcc = dcc;
-
-        if (TelephonyUtils.IS_DEBUGGABLE) {
-            IntentFilter filter = new IntentFilter();
-
-            filter.addAction(sActionDcTesterDeactivateAll);
-            log("register for intent action=" + sActionDcTesterDeactivateAll);
-
-            filter.addAction(mPhone.getActionDetached());
-            log("register for intent action=" + mPhone.getActionDetached());
-
-            phone.getContext().registerReceiver(sIntentReceiver, filter, null, handler,
-                    Context.RECEIVER_EXPORTED);
-        }
-    }
-
-    void dispose() {
-        if (TelephonyUtils.IS_DEBUGGABLE) {
-            mPhone.getContext().unregisterReceiver(sIntentReceiver);
-        }
-    }
-
-    private static void log(String s) {
-        Rlog.d(LOG_TAG, s);
-    }
-}
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcTesterFailBringUpAll.java b/src/java/com/android/internal/telephony/dataconnection/DcTesterFailBringUpAll.java
deleted file mode 100644
index 788da29..0000000
--- a/src/java/com/android/internal/telephony/dataconnection/DcTesterFailBringUpAll.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2013 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.dataconnection;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Handler;
-import android.telephony.DataFailCause;
-
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.util.TelephonyUtils;
-import com.android.telephony.Rlog;
-
-/**
- * A package level call that causes all DataConnection bringUp calls to fail a specific
- * number of times. Here is an example that sets counter to 2 and cause to -3 for all instances:
- *    adb shell am broadcast -a com.android.internal.telephony.dataconnection.action_fail_bringup \
- *     --ei counter 2 --ei fail_cause -3
- *
- * Also you can add a suggested retry time if desired:
- *     --ei suggested_retry_time 5000
- *
- * The fail_cause is one of {@link DataFailCause}
- */
-public class DcTesterFailBringUpAll {
-    private static final String LOG_TAG = "DcTesterFailBrinupAll";
-    private static final boolean DBG = true;
-
-    private Phone mPhone;
-
-    private String mActionFailBringUp = DcFailBringUp.INTENT_BASE + "."
-            + DcFailBringUp.ACTION_FAIL_BRINGUP;
-
-    // The saved FailBringUp data from the intent
-    private DcFailBringUp mFailBringUp = new DcFailBringUp();
-
-    // The static intent receiver one for all instances.
-    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
-            @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (DBG) log("sIntentReceiver.onReceive: action=" + action);
-            if (action.equals(mActionFailBringUp)) {
-                mFailBringUp.saveParameters(intent, "sFailBringUp");
-            } else if (action.equals(mPhone.getActionDetached())) {
-                // Counter is MAX, bringUp/retry will always fail
-                log("simulate detaching");
-                mFailBringUp.saveParameters(Integer.MAX_VALUE,
-                        DataFailCause.LOST_CONNECTION,
-                        DcFailBringUp.DEFAULT_SUGGESTED_RETRY_TIME);
-            } else if (action.equals(mPhone.getActionAttached())) {
-                // Counter is 0 next bringUp/retry will succeed
-                log("simulate attaching");
-                mFailBringUp.saveParameters(0, DataFailCause.NONE,
-                        DcFailBringUp.DEFAULT_SUGGESTED_RETRY_TIME);
-            } else {
-                if (DBG) log("onReceive: unknown action=" + action);
-            }
-        }
-    };
-
-    DcTesterFailBringUpAll(Phone phone, Handler handler) {
-        mPhone = phone;
-        if (TelephonyUtils.IS_DEBUGGABLE) {
-            IntentFilter filter = new IntentFilter();
-
-            filter.addAction(mActionFailBringUp);
-            log("register for intent action=" + mActionFailBringUp);
-
-            filter.addAction(mPhone.getActionDetached());
-            log("register for intent action=" + mPhone.getActionDetached());
-
-            filter.addAction(mPhone.getActionAttached());
-            log("register for intent action=" + mPhone.getActionAttached());
-
-            phone.getContext().registerReceiver(mIntentReceiver, filter, null, handler,
-                    Context.RECEIVER_EXPORTED);
-        }
-    }
-
-    void dispose() {
-        if (TelephonyUtils.IS_DEBUGGABLE) {
-            mPhone.getContext().unregisterReceiver(mIntentReceiver);
-        }
-    }
-
-    public DcFailBringUp getDcFailBringUp() {
-        return mFailBringUp;
-    }
-
-    private void log(String s) {
-        Rlog.d(LOG_TAG, s);
-    }
-}
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
deleted file mode 100755
index 28f69dc..0000000
--- a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
+++ /dev/null
@@ -1,5665 +0,0 @@
-/*
- * Copyright (C) 2006 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.dataconnection;
-
-import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
-import static android.net.NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_CONGESTED;
-import static android.net.NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_UNMETERED;
-import static android.telephony.TelephonyManager.NETWORK_TYPE_LTE;
-import static android.telephony.TelephonyManager.NETWORK_TYPE_NR;
-import static android.telephony.data.DataCallResponse.HANDOVER_FAILURE_MODE_DO_FALLBACK;
-import static android.telephony.data.DataCallResponse.HANDOVER_FAILURE_MODE_LEGACY;
-import static android.telephony.data.DataCallResponse.HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL;
-
-import static com.android.internal.telephony.RILConstants.DATA_PROFILE_DEFAULT;
-import static com.android.internal.telephony.RILConstants.DATA_PROFILE_INVALID;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.AlarmManager;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.ProgressDialog;
-import android.content.ActivityNotFoundException;
-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.database.ContentObserver;
-import android.database.Cursor;
-import android.net.ConnectivityManager;
-import android.net.LinkProperties;
-import android.net.NetworkAgent;
-import android.net.NetworkCapabilities;
-import android.net.NetworkPolicyManager;
-import android.net.NetworkRequest;
-import android.net.TrafficStats;
-import android.net.Uri;
-import android.os.AsyncResult;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Message;
-import android.os.PersistableBundle;
-import android.os.RegistrantList;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.preference.PreferenceManager;
-import android.provider.Settings;
-import android.provider.Settings.SettingNotFoundException;
-import android.provider.Telephony;
-import android.telephony.AccessNetworkConstants;
-import android.telephony.AccessNetworkConstants.TransportType;
-import android.telephony.Annotation.ApnType;
-import android.telephony.Annotation.DataFailureCause;
-import android.telephony.Annotation.NetworkType;
-import android.telephony.CarrierConfigManager;
-import android.telephony.CellLocation;
-import android.telephony.DataFailCause;
-import android.telephony.NetworkRegistrationInfo;
-import android.telephony.PcoData;
-import android.telephony.PreciseDataConnectionState;
-import android.telephony.ServiceState;
-import android.telephony.ServiceState.RilRadioTechnology;
-import android.telephony.SubscriptionManager;
-import android.telephony.SubscriptionPlan;
-import android.telephony.TelephonyDisplayInfo;
-import android.telephony.TelephonyFrameworkInitializer;
-import android.telephony.TelephonyManager;
-import android.telephony.TelephonyManager.SimState;
-import android.telephony.cdma.CdmaCellLocation;
-import android.telephony.data.ApnSetting;
-import android.telephony.data.DataCallResponse;
-import android.telephony.data.DataCallResponse.HandoverFailureMode;
-import android.telephony.data.DataProfile;
-import android.telephony.data.ThrottleStatus;
-import android.telephony.gsm.GsmCellLocation;
-import android.text.TextUtils;
-import android.util.EventLog;
-import android.util.LocalLog;
-import android.util.Log;
-import android.util.Pair;
-import android.util.SparseArray;
-import android.view.WindowManager;
-
-import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.DctConstants;
-import com.android.internal.telephony.EventLogTags;
-import com.android.internal.telephony.GsmCdmaPhone;
-import com.android.internal.telephony.ITelephony;
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.PhoneFactory;
-import com.android.internal.telephony.RILConstants;
-import com.android.internal.telephony.RetryManager;
-import com.android.internal.telephony.SettingsObserver;
-import com.android.internal.telephony.SubscriptionInfoUpdater;
-import com.android.internal.telephony.data.DataConfigManager;
-import com.android.internal.telephony.data.PhoneSwitcher;
-import com.android.internal.telephony.dataconnection.DataConnectionReasons.DataAllowedReasonType;
-import com.android.internal.telephony.dataconnection.DataConnectionReasons.DataDisallowedReasonType;
-import com.android.internal.telephony.dataconnection.DataEnabledSettings.DataEnabledChangedReason;
-import com.android.internal.telephony.metrics.DataStallRecoveryStats;
-import com.android.internal.telephony.metrics.TelephonyMetrics;
-import com.android.internal.telephony.util.ArrayUtils;
-import com.android.internal.telephony.util.NotificationChannelController;
-import com.android.internal.telephony.util.TelephonyUtils;
-import com.android.internal.util.AsyncChannel;
-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.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.stream.Collectors;
-
-/**
- * {@hide}
- */
-public class DcTracker extends Handler {
-    protected static final boolean DBG = true;
-    private static final boolean VDBG = false; // STOPSHIP if true
-    private static final boolean VDBG_STALL = false; // STOPSHIP if true
-    private static final boolean RADIO_TESTS = false;
-    private static final String NOTIFICATION_TAG = DcTracker.class.getSimpleName();
-
-    @IntDef(value = {
-            REQUEST_TYPE_NORMAL,
-            REQUEST_TYPE_HANDOVER,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface RequestNetworkType {}
-
-    /**
-     * Normal request for {@link #requestNetwork(NetworkRequest, int, Message)}. For request
-     * network, this adds the request to the {@link ApnContext}. If there were no network request
-     * attached to the {@link ApnContext} earlier, this request setups a data connection.
-     */
-    public static final int REQUEST_TYPE_NORMAL = 1;
-
-    /**
-     * Handover request for {@link #requestNetwork(NetworkRequest, int, Message)} or
-     * {@link #releaseNetwork(NetworkRequest, int)}. For request network, this
-     * initiates the handover data setup process. The existing data connection will be seamlessly
-     * handover to the new network. For release network, this performs a data connection softly
-     * clean up at the underlying layer (versus normal data release).
-     */
-    public static final int REQUEST_TYPE_HANDOVER = 2;
-
-    @IntDef(value = {
-            RELEASE_TYPE_NORMAL,
-            RELEASE_TYPE_DETACH,
-            RELEASE_TYPE_HANDOVER,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ReleaseNetworkType {}
-
-    /**
-     * For release network, this is just removing the network request from the {@link ApnContext}.
-     * Note this does not tear down the physical data connection. Normally the data connection is
-     * torn down by connectivity service directly calling {@link NetworkAgent#unwanted()}.
-     */
-    public static final int RELEASE_TYPE_NORMAL = 1;
-
-    /**
-     * Detach request for {@link #releaseNetwork(NetworkRequest, int)} only. This
-     * forces the APN context detach from the data connection. If this {@link ApnContext} is the
-     * last one attached to the data connection, the data connection will be torn down, otherwise
-     * the data connection remains active.
-     */
-    public static final int RELEASE_TYPE_DETACH = 2;
-
-    /**
-     * Handover request for {@link #releaseNetwork(NetworkRequest, int)}. For release
-     * network, this performs a data connection softly clean up at the underlying layer (versus
-     * normal data release).
-     */
-    public static final int RELEASE_TYPE_HANDOVER = 3;
-
-    /** The extras for handover completion message */
-    public static final String DATA_COMPLETE_MSG_EXTRA_NETWORK_REQUEST = "extra_network_request";
-    public static final String DATA_COMPLETE_MSG_EXTRA_TRANSPORT_TYPE = "extra_transport_type";
-    public static final String DATA_COMPLETE_MSG_EXTRA_SUCCESS = "extra_success";
-    /**
-     * The flag indicates whether after handover failure, the data connection should remain on the
-     * original transport.
-     */
-    public static final String DATA_COMPLETE_MSG_EXTRA_HANDOVER_FAILURE_FALLBACK =
-            "extra_handover_failure_fallback";
-
-    private final String mLogTag;
-
-    public AtomicBoolean isCleanupRequired = new AtomicBoolean(false);
-
-    private final TelephonyManager mTelephonyManager;
-
-    private final AlarmManager mAlarmManager;
-
-    /* Currently requested APN type (TODO: This should probably be a parameter not a member) */
-    private int mRequestedApnType = ApnSetting.TYPE_DEFAULT;
-
-    // All data enabling/disabling related settings
-    private final DataEnabledSettings mDataEnabledSettings;
-
-    /**
-     * After detecting a potential connection problem, this is the max number
-     * of subsequent polls before attempting recovery.
-     */
-    // 1 sec. default polling interval when screen is on.
-    private static final int POLL_NETSTAT_MILLIS = 1000;
-    // 10 min. default polling interval when screen is off.
-    private static final int POLL_NETSTAT_SCREEN_OFF_MILLIS = 1000*60*10;
-    // Default sent packets without ack which triggers initial recovery steps
-    private static final int NUMBER_SENT_PACKETS_OF_HANG = 10;
-
-    // Default for the data stall alarm while non-aggressive stall detection
-    private static final int DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS_DEFAULT = 1000 * 60 * 6;
-    // Default for the data stall alarm for aggressive stall detection
-    private static final int DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS_DEFAULT = 1000 * 60;
-
-    private static final boolean DATA_STALL_SUSPECTED = true;
-    protected static final boolean DATA_STALL_NOT_SUSPECTED = false;
-
-    private static final String INTENT_DATA_STALL_ALARM =
-            "com.android.internal.telephony.data-stall";
-    // Tag for tracking stale alarms
-    private static final String INTENT_DATA_STALL_ALARM_EXTRA_TAG = "data_stall_alarm_extra_tag";
-    private static final String INTENT_DATA_STALL_ALARM_EXTRA_TRANSPORT_TYPE =
-            "data_stall_alarm_extra_transport_type";
-
-    // Unique id for no data notification on setup data permanently failed.
-    private static final int NO_DATA_NOTIFICATION = 1001;
-
-    /** The higher index has higher priority. */
-    private static final DctConstants.State[] DATA_CONNECTION_STATE_PRIORITIES = {
-            DctConstants.State.IDLE,
-            DctConstants.State.DISCONNECTING,
-            DctConstants.State.CONNECTING,
-            DctConstants.State.CONNECTED,
-    };
-
-    private DcTesterFailBringUpAll mDcTesterFailBringUpAll;
-    private DcController mDcc;
-
-    /** kept in sync with mApnContexts
-     * Higher numbers are higher priority and sorted so highest priority is first */
-    private ArrayList<ApnContext> mPrioritySortedApnContexts = new ArrayList<>();
-
-    /** all APN settings applicable to the current carrier */
-    private ArrayList<ApnSetting> mAllApnSettings = new ArrayList<>();
-
-    /** preferred apn */
-    private ApnSetting mPreferredApn = null;
-
-    /** Is packet service restricted by network */
-    private boolean mIsPsRestricted = false;
-
-    /** emergency apn Setting*/
-    private ApnSetting mEmergencyApn = null;
-
-    /* Once disposed dont handle any messages */
-    private boolean mIsDisposed = false;
-
-    private ContentResolver mResolver;
-
-    /* Set to true with CMD_ENABLE_MOBILE_PROVISIONING */
-    private boolean mIsProvisioning = false;
-
-    /* The Url passed as object parameter in CMD_ENABLE_MOBILE_PROVISIONING */
-    private String mProvisioningUrl = null;
-
-    /* Indicating data service is bound or not */
-    private boolean mDataServiceBound = false;
-
-    /* Intent to hide/show the sign-in error notification for provisioning */
-    private static final String INTENT_PROVISION = "com.android.internal.telephony.PROVISION";
-
-    /**
-     * Extra containing the phone ID for INTENT_PROVISION
-     * Must be kept consistent with NetworkNotificationManager#setProvNotificationVisible.
-     * TODO: refactor the deprecated API to prevent hardcoding values.
-     */
-    private static final String EXTRA_PROVISION_PHONE_ID = "provision.phone.id";
-
-    /* Intent for the provisioning apn alarm */
-    private static final String INTENT_PROVISIONING_APN_ALARM =
-            "com.android.internal.telephony.provisioning_apn_alarm";
-
-    /* Tag for tracking stale alarms */
-    private static final String PROVISIONING_APN_ALARM_TAG_EXTRA = "provisioning.apn.alarm.tag";
-
-    /* Debug property for overriding the PROVISIONING_APN_ALARM_DELAY_IN_MS */
-    private static final String DEBUG_PROV_APN_ALARM = "persist.debug.prov_apn_alarm";
-
-    /* Default for the provisioning apn alarm timeout */
-    private static final int PROVISIONING_APN_ALARM_DELAY_IN_MS_DEFAULT = 1000 * 60 * 15;
-
-    /* The provision apn alarm intent used to disable the provisioning apn */
-    private PendingIntent mProvisioningApnAlarmIntent = null;
-
-    /* Used to track stale provisioning apn alarms */
-    private int mProvisioningApnAlarmTag = (int) SystemClock.elapsedRealtime();
-
-    private AsyncChannel mReplyAc = new AsyncChannel();
-
-    private final LocalLog mDataRoamingLeakageLog = new LocalLog(32);
-    private final LocalLog mApnSettingsInitializationLog = new LocalLog(32);
-
-    /* 5G connection reevaluation watchdog alarm constants */
-    private long mWatchdogTimeMs = 1000 * 60 * 60;
-    private boolean mWatchdog = false;
-
-    /* Default for whether 5G frequencies are considered unmetered */
-    private boolean mNrNsaAllUnmetered = false;
-    private boolean mNrNsaMmwaveUnmetered = false;
-    private boolean mNrNsaSub6Unmetered = false;
-    private boolean mNrSaAllUnmetered = false;
-    private boolean mNrSaMmwaveUnmetered = false;
-    private boolean mNrSaSub6Unmetered = false;
-    private boolean mNrNsaRoamingUnmetered = false;
-
-    // it effect the PhysicalLinkStatusChanged
-    private boolean mLteEndcUsingUserDataForRrcDetection = false;
-
-    /* List of SubscriptionPlans, updated when initialized and when plans are changed. */
-    private List<SubscriptionPlan> mSubscriptionPlans = new ArrayList<>();
-    /* List of network types an unmetered override applies to, set by onSubscriptionOverride
-     * and cleared when the device is rebooted or the override expires. */
-    private List<Integer> mUnmeteredNetworkTypes = null;
-    /* List of network types a congested override applies to, set by onSubscriptionOverride
-     * and cleared when the device is rebooted or the override expires. */
-    private List<Integer> mCongestedNetworkTypes = null;
-    /* Whether an unmetered override is currently active. */
-    private boolean mUnmeteredOverride = false;
-    /* Whether a congested override is currently active. */
-    private boolean mCongestedOverride = false;
-
-    @SimState
-    private int mSimState = TelephonyManager.SIM_STATE_UNKNOWN;
-
-    private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver () {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-
-            if (action.equals(Intent.ACTION_SCREEN_ON)) {
-                // TODO: Evaluate hooking this up with DeviceStateMonitor
-                if (DBG) log("screen on");
-                mIsScreenOn = true;
-                stopNetStatPoll();
-                startNetStatPoll();
-                restartDataStallAlarm();
-            } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
-                if (DBG) log("screen off");
-                mIsScreenOn = false;
-                stopNetStatPoll();
-                startNetStatPoll();
-                restartDataStallAlarm();
-            } else if (action.equals(INTENT_DATA_STALL_ALARM)) {
-                onActionIntentDataStallAlarm(intent);
-            } else if (action.equals(INTENT_PROVISIONING_APN_ALARM)) {
-                if (DBG) log("Provisioning apn alarm");
-                onActionIntentProvisioningApnAlarm(intent);
-            } else if (action.equals(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED)
-                    || action.equals(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED)) {
-                if (mPhone.getPhoneId() == intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX,
-                        SubscriptionManager.INVALID_SIM_SLOT_INDEX)) {
-                    int simState = intent.getIntExtra(TelephonyManager.EXTRA_SIM_STATE,
-                            TelephonyManager.SIM_STATE_UNKNOWN);
-                    sendMessage(obtainMessage(DctConstants.EVENT_SIM_STATE_UPDATED, simState, 0));
-                }
-            } else if (action.equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
-                if (mPhone.getPhoneId() == intent.getIntExtra(CarrierConfigManager.EXTRA_SLOT_INDEX,
-                        SubscriptionManager.INVALID_SIM_SLOT_INDEX)) {
-                    if (intent.getBooleanExtra(
-                            CarrierConfigManager.EXTRA_REBROADCAST_ON_UNLOCK, false)) {
-                        // Ignore the rebroadcast one to prevent multiple carrier config changed
-                        // event during boot up.
-                        return;
-                    }
-                    int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
-                            SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-                    if (SubscriptionManager.isValidSubscriptionId(subId)) {
-                        sendEmptyMessage(DctConstants.EVENT_CARRIER_CONFIG_CHANGED);
-                    }
-                }
-            } else {
-                if (DBG) log("onReceive: Unknown action=" + action);
-            }
-        }
-    };
-
-    private final Runnable mPollNetStat = new Runnable() {
-        @Override
-        public void run() {
-            updateDataActivity();
-
-            if (mIsScreenOn) {
-                mNetStatPollPeriod = Settings.Global.getInt(mResolver,
-                        Settings.Global.PDP_WATCHDOG_POLL_INTERVAL_MS, POLL_NETSTAT_MILLIS);
-            } else {
-                mNetStatPollPeriod = Settings.Global.getInt(mResolver,
-                        Settings.Global.PDP_WATCHDOG_LONG_POLL_INTERVAL_MS,
-                        POLL_NETSTAT_SCREEN_OFF_MILLIS);
-            }
-
-            if (mNetStatPollEnabled) {
-                mDataConnectionTracker.postDelayed(this, mNetStatPollPeriod);
-            }
-        }
-    };
-
-    private class ThrottleStatusChangedCallback implements DataThrottler.Callback {
-        @Override
-        public void onThrottleStatusChanged(List<ThrottleStatus> throttleStatuses) {
-            for (ThrottleStatus status : throttleStatuses) {
-                if (status.getThrottleType() == ThrottleStatus.THROTTLE_TYPE_NONE) {
-                    setupDataOnConnectableApn(mApnContextsByType.get(status.getApnType()),
-                            Phone.REASON_DATA_UNTHROTTLED,
-                            RetryFailures.ALWAYS);
-                }
-            }
-        }
-    }
-
-    private NetworkPolicyManager mNetworkPolicyManager;
-    private final NetworkPolicyManager.SubscriptionCallback mSubscriptionCallback =
-            new NetworkPolicyManager.SubscriptionCallback() {
-        @Override
-        public void onSubscriptionOverride(int subId, int overrideMask, int overrideValue,
-                int[] networkTypes) {
-            if (mPhone == null || mPhone.getSubId() != subId) return;
-
-            List<Integer> tempList = new ArrayList<>();
-            for (int networkType : networkTypes) {
-                tempList.add(networkType);
-            }
-
-            log("Subscription override: overrideMask=" + overrideMask
-                    + ", overrideValue=" + overrideValue + ", networkTypes=" + tempList);
-
-            if (overrideMask == SUBSCRIPTION_OVERRIDE_UNMETERED) {
-                mUnmeteredNetworkTypes = tempList;
-                mUnmeteredOverride = overrideValue != 0;
-                reevaluateUnmeteredConnections();
-            } else if (overrideMask == SUBSCRIPTION_OVERRIDE_CONGESTED) {
-                mCongestedNetworkTypes = tempList;
-                mCongestedOverride = overrideValue != 0;
-                reevaluateCongestedConnections();
-            }
-        }
-
-        @Override
-        public void onSubscriptionPlansChanged(int subId, SubscriptionPlan[] plans) {
-            if (mPhone == null || mPhone.getSubId() != subId) return;
-
-            mSubscriptionPlans = Arrays.asList(plans);
-            if (DBG) log("SubscriptionPlans changed: " + mSubscriptionPlans);
-            reevaluateUnmeteredConnections();
-        }
-    };
-
-    private final SettingsObserver mSettingsObserver;
-
-    private void registerSettingsObserver() {
-        mSettingsObserver.unobserve();
-        String simSuffix = "";
-        if (TelephonyManager.getDefault().getSimCount() > 1) {
-            simSuffix = Integer.toString(mPhone.getSubId());
-        }
-
-        mSettingsObserver.observe(
-                Settings.Global.getUriFor(Settings.Global.DATA_ROAMING + simSuffix),
-                DctConstants.EVENT_ROAMING_SETTING_CHANGE);
-        mSettingsObserver.observe(
-                Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
-                DctConstants.EVENT_DEVICE_PROVISIONED_CHANGE);
-    }
-
-    /**
-     * Maintain the sum of transmit and receive packets.
-     *
-     * The packet counts are initialized and reset to -1 and
-     * remain -1 until they can be updated.
-     */
-    public static class TxRxSum {
-        public long txPkts;
-        public long rxPkts;
-
-        public TxRxSum() {
-            reset();
-        }
-
-        public TxRxSum(long txPkts, long rxPkts) {
-            this.txPkts = txPkts;
-            this.rxPkts = rxPkts;
-        }
-
-        public TxRxSum(TxRxSum sum) {
-            txPkts = sum.txPkts;
-            rxPkts = sum.rxPkts;
-        }
-
-        public void reset() {
-            txPkts = -1;
-            rxPkts = -1;
-        }
-
-        @Override
-        public String toString() {
-            return "{txSum=" + txPkts + " rxSum=" + rxPkts + "}";
-        }
-
-        /**
-         * Get total Tx/Rx packet count from TrafficStats
-         */
-        public void updateTotalTxRxSum() {
-            this.txPkts = TrafficStats.getMobileTxPackets();
-            this.rxPkts = TrafficStats.getMobileRxPackets();
-        }
-    }
-
-    private void onDataReconnect(ApnContext apnContextforRetry, int subId,
-            @RequestNetworkType int requestType) {
-        int phoneSubId = mPhone.getSubId();
-        String apnType = apnContextforRetry.getApnType();
-        String reason =  apnContextforRetry.getReason();
-
-        if (!SubscriptionManager.isValidSubscriptionId(subId) || (subId != phoneSubId)) {
-            log("onDataReconnect: invalid subId");
-            return;
-        }
-
-        ApnContext apnContext = mApnContexts.get(apnType);
-
-        if (DBG) {
-            log("onDataReconnect: mState=" + mState + " reason=" + reason + " apnType=" + apnType
-                    + " apnContext=" + apnContext);
-        }
-
-        if ((apnContext != null) && (apnContext.isEnabled())) {
-            apnContext.setReason(reason);
-            DctConstants.State apnContextState = apnContext.getState();
-            if (DBG) {
-                log("onDataReconnect: apnContext state=" + apnContextState);
-            }
-            if ((apnContextState == DctConstants.State.FAILED)
-                    || (apnContextState == DctConstants.State.IDLE)) {
-                if (DBG) {
-                    log("onDataReconnect: state is FAILED|IDLE, disassociate");
-                }
-                apnContext.releaseDataConnection("");
-            } else {
-                if (DBG) log("onDataReconnect: keep associated");
-            }
-            // TODO: IF already associated should we send the EVENT_TRY_SETUP_DATA???
-            sendMessage(obtainMessage(DctConstants.EVENT_TRY_SETUP_DATA, requestType,
-                    0, apnContext));
-        }
-    }
-
-    private void onActionIntentDataStallAlarm(Intent intent) {
-        if (VDBG_STALL) log("onActionIntentDataStallAlarm: action=" + intent.getAction());
-
-        int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
-                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-        if (!SubscriptionManager.isValidSubscriptionId(subId) || (subId != mPhone.getSubId())) {
-            return;
-        }
-
-        int transportType = intent.getIntExtra(INTENT_DATA_STALL_ALARM_EXTRA_TRANSPORT_TYPE, 0);
-        if (transportType != mTransportType) {
-            return;
-        }
-
-        Message msg = obtainMessage(DctConstants.EVENT_DATA_STALL_ALARM,
-                intent.getAction());
-        msg.arg1 = intent.getIntExtra(INTENT_DATA_STALL_ALARM_EXTRA_TAG, 0);
-        sendMessage(msg);
-    }
-
-    private RegistrantList mAllDataDisconnectedRegistrants = new RegistrantList();
-
-    // member variables
-    protected final Phone mPhone;
-    private DctConstants.Activity mActivity = DctConstants.Activity.NONE;
-    private DctConstants.State mState = DctConstants.State.IDLE;
-    private final Handler mDataConnectionTracker;
-
-    private long mTxPkts;
-    private long mRxPkts;
-    private int mNetStatPollPeriod;
-    private boolean mNetStatPollEnabled = false;
-
-    private TxRxSum mDataStallTxRxSum = new TxRxSum(0, 0);
-    // Used to track stale data stall alarms.
-    private int mDataStallAlarmTag = (int) SystemClock.elapsedRealtime();
-    // The current data stall alarm intent
-    private PendingIntent mDataStallAlarmIntent = null;
-    // Number of packets sent since the last received packet
-    private long mSentSinceLastRecv;
-    // Controls when a simple recovery attempt it to be tried
-    private int mNoRecvPollCount = 0;
-    // Reference counter for enabling fail fast
-    private static int sEnableFailFastRefCounter = 0;
-    // True if data stall detection is enabled
-    private volatile boolean mDataStallNoRxEnabled = true;
-
-    protected volatile boolean mFailFast = false;
-
-    // True when in voice call
-    protected boolean mInVoiceCall = false;
-
-    /** Intent sent when the reconnect alarm fires. */
-    private PendingIntent mReconnectIntent = null;
-
-    // When false we will not auto attach and manually attaching is required.
-    protected boolean mAutoAttachOnCreationConfig = false;
-    private AtomicBoolean mAutoAttachEnabled = new AtomicBoolean(false);
-
-    // State of screen
-    // (TODO: Reconsider tying directly to screen, maybe this is
-    //        really a lower power mode")
-    private boolean mIsScreenOn = true;
-
-    /** Allows the generation of unique Id's for DataConnection objects */
-    private AtomicInteger mUniqueIdGenerator = new AtomicInteger(0);
-
-    /** The data connections. */
-    private HashMap<Integer, DataConnection> mDataConnections =
-            new HashMap<Integer, DataConnection>();
-
-    /** Convert an ApnType string to Id (TODO: Use "enumeration" instead of String for ApnType) */
-    private HashMap<String, Integer> mApnToDataConnectionId = new HashMap<String, Integer>();
-
-    /** Phone.APN_TYPE_* ===> ApnContext */
-    protected ConcurrentHashMap<String, ApnContext> mApnContexts =
-            new ConcurrentHashMap<String, ApnContext>();
-
-    private SparseArray<ApnContext> mApnContextsByType = new SparseArray<ApnContext>();
-
-    private ArrayList<DataProfile> mLastDataProfileList = new ArrayList<>();
-
-    /** RAT name ===> (downstream, upstream) bandwidth values from carrier config. */
-    private ConcurrentHashMap<String, Pair<Integer, Integer>> mBandwidths =
-            new ConcurrentHashMap<>();
-
-    private boolean mConfigReady = false;
-
-    /**
-     * Handles changes to the APN db.
-     */
-    private class ApnChangeObserver extends ContentObserver {
-        public ApnChangeObserver () {
-            super(mDataConnectionTracker);
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            sendMessage(obtainMessage(DctConstants.EVENT_APN_CHANGED));
-        }
-    }
-
-    //***** Instance Variables
-
-    private boolean mReregisterOnReconnectFailure = false;
-
-
-    //***** Constants
-
-    private static final int PROVISIONING_SPINNER_TIMEOUT_MILLIS = 120 * 1000;
-
-    static final Uri PREFERAPN_NO_UPDATE_URI_USING_SUBID =
-                        Uri.parse("content://telephony/carriers/preferapn_no_update/subId/");
-    static final String APN_ID = "apn_id";
-
-    private boolean mCanSetPreferApn = false;
-
-    private AtomicBoolean mAttached = new AtomicBoolean(false);
-
-    /** Watches for changes to the APN db. */
-    private ApnChangeObserver mApnObserver;
-
-    private BroadcastReceiver mProvisionBroadcastReceiver;
-    private ProgressDialog mProvisioningSpinner;
-
-    private final DataServiceManager mDataServiceManager;
-
-    @AccessNetworkConstants.TransportType
-    private final int mTransportType;
-
-    private DataStallRecoveryHandler mDsRecoveryHandler;
-    private HandlerThread mHandlerThread;
-
-    private final DataThrottler mDataThrottler;
-
-    private final ThrottleStatusChangedCallback mThrottleStatusCallback;
-
-    /**
-     * Request network completion message map. Key is the APN type, value is the list of completion
-     * messages to be sent. Using a list because there might be multiple network requests for
-     * the same APN type.
-     */
-    private final Map<Integer, List<Message>> mHandoverCompletionMsgs = new HashMap<>();
-
-    //***** Constructor
-    public DcTracker(Phone phone, @TransportType int transportType) {
-        super();
-        mPhone = phone;
-        if (DBG) log("DCT.constructor");
-        mTelephonyManager = TelephonyManager.from(phone.getContext())
-                .createForSubscriptionId(phone.getSubId());
-        // The 'C' in tag indicates cellular, and 'I' indicates IWLAN. This is to distinguish
-        // between two DcTrackers, one for each.
-        String tagSuffix = "-" + ((transportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
-                ? "C" : "I");
-        tagSuffix += "-" + mPhone.getPhoneId();
-        mLogTag = "DCT" + tagSuffix;
-
-        mTransportType = transportType;
-        mDataServiceManager = new DataServiceManager(phone, transportType, tagSuffix);
-        mDataThrottler = new DataThrottler(mPhone, transportType);
-
-        mResolver = mPhone.getContext().getContentResolver();
-        mAlarmManager =
-                (AlarmManager) mPhone.getContext().getSystemService(Context.ALARM_SERVICE);
-
-        mDsRecoveryHandler = new DataStallRecoveryHandler();
-
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_SCREEN_ON);
-        filter.addAction(Intent.ACTION_SCREEN_OFF);
-        filter.addAction(INTENT_DATA_STALL_ALARM);
-        filter.addAction(INTENT_PROVISIONING_APN_ALARM);
-        filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-        filter.addAction(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED);
-        filter.addAction(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED);
-
-        mDataEnabledSettings = mPhone.getDataEnabledSettings();
-
-        mDataEnabledSettings.registerForDataEnabledChanged(this,
-                DctConstants.EVENT_DATA_ENABLED_CHANGED, null);
-        mDataEnabledSettings.registerForDataEnabledOverrideChanged(this,
-                DctConstants.EVENT_DATA_ENABLED_OVERRIDE_RULES_CHANGED);
-
-        mPhone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone);
-
-        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mPhone.getContext());
-        mAutoAttachEnabled.set(sp.getBoolean(Phone.DATA_DISABLED_ON_BOOT_KEY, false));
-
-        mNetworkPolicyManager = (NetworkPolicyManager) mPhone.getContext()
-                .getSystemService(Context.NETWORK_POLICY_SERVICE);
-        mNetworkPolicyManager.registerSubscriptionCallback(mSubscriptionCallback);
-
-        mHandlerThread = new HandlerThread("DcHandlerThread");
-        mHandlerThread.start();
-        Handler dcHandler = new Handler(mHandlerThread.getLooper());
-        mDcc = DcController.makeDcc(mPhone, this, mDataServiceManager, dcHandler.getLooper(),
-                tagSuffix);
-        mDcTesterFailBringUpAll = new DcTesterFailBringUpAll(mPhone, dcHandler);
-
-        mDataConnectionTracker = this;
-        registerForAllEvents();
-        mApnObserver = new ApnChangeObserver();
-        phone.getContext().getContentResolver().registerContentObserver(
-                Telephony.Carriers.CONTENT_URI, true, mApnObserver);
-
-        initApnContexts();
-
-        addDefaultApnSettingsAsNeeded();
-
-        mSettingsObserver = new SettingsObserver(mPhone.getContext(), this);
-        registerSettingsObserver();
-
-        mThrottleStatusCallback = new ThrottleStatusChangedCallback();
-        mDataThrottler.registerForThrottleStatusChanges(mThrottleStatusCallback);
-    }
-
-    @VisibleForTesting
-    public DcTracker() {
-        mLogTag = "DCT";
-        mTelephonyManager = null;
-        mAlarmManager = null;
-        mPhone = null;
-        mDataConnectionTracker = null;
-        mSettingsObserver = new SettingsObserver(null, this);
-        mDataEnabledSettings = null;
-        mTransportType = 0;
-        mDataServiceManager = null;
-        mDataThrottler = null;
-        mThrottleStatusCallback = null;
-    }
-
-    public void registerServiceStateTrackerEvents() {
-        mPhone.getServiceStateTracker().registerForDataConnectionAttached(mTransportType, this,
-                DctConstants.EVENT_DATA_CONNECTION_ATTACHED, null);
-        mPhone.getServiceStateTracker().registerForDataConnectionDetached(mTransportType, this,
-                DctConstants.EVENT_DATA_CONNECTION_DETACHED, null);
-        mPhone.getServiceStateTracker().registerForDataRoamingOn(this,
-                DctConstants.EVENT_ROAMING_ON, null);
-        mPhone.getServiceStateTracker().registerForDataRoamingOff(this,
-                DctConstants.EVENT_ROAMING_OFF, null, true);
-        mPhone.getServiceStateTracker().registerForPsRestrictedEnabled(this,
-                DctConstants.EVENT_PS_RESTRICT_ENABLED, null);
-        mPhone.getServiceStateTracker().registerForPsRestrictedDisabled(this,
-                DctConstants.EVENT_PS_RESTRICT_DISABLED, null);
-        mPhone.getServiceStateTracker().registerForDataRegStateOrRatChanged(mTransportType, this,
-                DctConstants.EVENT_DATA_RAT_CHANGED, null);
-    }
-
-    public void unregisterServiceStateTrackerEvents() {
-        mPhone.getServiceStateTracker().unregisterForDataConnectionAttached(mTransportType, this);
-        mPhone.getServiceStateTracker().unregisterForDataConnectionDetached(mTransportType, this);
-        mPhone.getServiceStateTracker().unregisterForDataRoamingOn(this);
-        mPhone.getServiceStateTracker().unregisterForDataRoamingOff(this);
-        mPhone.getServiceStateTracker().unregisterForPsRestrictedEnabled(this);
-        mPhone.getServiceStateTracker().unregisterForPsRestrictedDisabled(this);
-        mPhone.getServiceStateTracker().unregisterForDataRegStateOrRatChanged(mTransportType, this);
-        mPhone.getServiceStateTracker().unregisterForAirplaneModeChanged(this);
-    }
-
-    private void registerForAllEvents() {
-        if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
-            mPhone.mCi.registerForAvailable(this, DctConstants.EVENT_RADIO_AVAILABLE, null);
-            mPhone.mCi.registerForOffOrNotAvailable(this,
-                    DctConstants.EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
-            mPhone.mCi.registerForPcoData(this, DctConstants.EVENT_PCO_DATA_RECEIVED, null);
-        }
-
-        // Note, this is fragile - the Phone is now presenting a merged picture
-        // of PS (volte) & CS and by diving into its internals you're just seeing
-        // the CS data.  This works well for the purposes this is currently used for
-        // but that may not always be the case.  Should probably be redesigned to
-        // accurately reflect what we're really interested in (registerForCSVoiceCallEnded).
-        mPhone.getCallTracker().registerForVoiceCallEnded(this,
-                DctConstants.EVENT_VOICE_CALL_ENDED, null);
-        mPhone.getCallTracker().registerForVoiceCallStarted(this,
-                DctConstants.EVENT_VOICE_CALL_STARTED, null);
-        mPhone.getDisplayInfoController().registerForTelephonyDisplayInfoChanged(this,
-                DctConstants.EVENT_TELEPHONY_DISPLAY_INFO_CHANGED, null);
-        registerServiceStateTrackerEvents();
-        mDataServiceManager.registerForServiceBindingChanged(this,
-                DctConstants.EVENT_DATA_SERVICE_BINDING_CHANGED, null);
-        mDataServiceManager.registerForApnUnthrottled(this, DctConstants.EVENT_APN_UNTHROTTLED);
-    }
-
-    public void dispose() {
-        if (DBG) log("DCT.dispose");
-
-        if (mProvisionBroadcastReceiver != null) {
-            mPhone.getContext().unregisterReceiver(mProvisionBroadcastReceiver);
-            mProvisionBroadcastReceiver = null;
-        }
-        if (mProvisioningSpinner != null) {
-            mProvisioningSpinner.dismiss();
-            mProvisioningSpinner = null;
-        }
-
-        cleanUpAllConnectionsInternal(true, null);
-
-        mIsDisposed = true;
-        mPhone.getContext().unregisterReceiver(mIntentReceiver);
-        mSettingsObserver.unobserve();
-
-        mNetworkPolicyManager.unregisterSubscriptionCallback(mSubscriptionCallback);
-        mDcTesterFailBringUpAll.dispose();
-
-        mPhone.getContext().getContentResolver().unregisterContentObserver(mApnObserver);
-        mApnContexts.clear();
-        mApnContextsByType.clear();
-        mPrioritySortedApnContexts.clear();
-        unregisterForAllEvents();
-
-        destroyDataConnections();
-    }
-
-    /**
-     * Stop the internal handler thread
-     *
-     * TESTING ONLY
-     */
-    @VisibleForTesting
-    public void stopHandlerThread() {
-        mHandlerThread.quit();
-    }
-
-    private void unregisterForAllEvents() {
-         //Unregister for all events
-        if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
-            mPhone.mCi.unregisterForAvailable(this);
-            mPhone.mCi.unregisterForOffOrNotAvailable(this);
-            mPhone.mCi.unregisterForPcoData(this);
-        }
-
-        mPhone.getCallTracker().unregisterForVoiceCallEnded(this);
-        mPhone.getCallTracker().unregisterForVoiceCallStarted(this);
-        mPhone.getDisplayInfoController().unregisterForTelephonyDisplayInfoChanged(this);
-        unregisterServiceStateTrackerEvents();
-        mDataServiceManager.unregisterForServiceBindingChanged(this);
-        mDataEnabledSettings.unregisterForDataEnabledChanged(this);
-        mDataEnabledSettings.unregisterForDataEnabledOverrideChanged(this);
-        mDataServiceManager.unregisterForApnUnthrottled(this);
-    }
-
-    /**
-     * Reevaluate existing data connections when conditions change.
-     *
-     * For example, handle reverting restricted networks back to unrestricted. If we're changing
-     * user data to enabled and this makes data truly enabled (not disabled by other factors) we
-     * need to reevaluate and possibly add NET_CAPABILITY_NOT_RESTRICTED capability to the data
-     * connection. This allows non-privilege apps to use the network.
-     *
-     * Or when we brought up a unmetered data connection while data is off, we only limit this
-     * data connection for unmetered use only. When data is turned back on, we need to tear that
-     * down so a full capable data connection can be re-established.
-     */
-    private void reevaluateDataConnections() {
-        for (DataConnection dataConnection : mDataConnections.values()) {
-            dataConnection.reevaluateRestrictedState();
-        }
-    }
-
-    public long getSubId() {
-        return mPhone.getSubId();
-    }
-
-    public DctConstants.Activity getActivity() {
-        return mActivity;
-    }
-
-    private void setActivity(DctConstants.Activity activity) {
-        log("setActivity = " + activity);
-        mActivity = activity;
-        mPhone.notifyDataActivity();
-    }
-
-    /**
-     * Request a network
-     *
-     * @param networkRequest Network request from clients
-     * @param type The request type
-     * @param onHandoverCompleteMsg When request type is handover, this message will be sent when
-     * handover is completed. For normal request, this should be null.
-     */
-    public void requestNetwork(NetworkRequest networkRequest, @RequestNetworkType int type,
-            Message onHandoverCompleteMsg) {
-        if (type != REQUEST_TYPE_HANDOVER && onHandoverCompleteMsg != null) {
-            throw new RuntimeException("request network with normal type request type but passing "
-                    + "handover complete message.");
-        }
-        final int apnType = ApnContext.getApnTypeFromNetworkRequest(networkRequest);
-        final ApnContext apnContext = mApnContextsByType.get(apnType);
-        if (apnContext != null) {
-            apnContext.requestNetwork(networkRequest, type, onHandoverCompleteMsg);
-        }
-    }
-
-    public void releaseNetwork(NetworkRequest networkRequest, @ReleaseNetworkType int type) {
-        final int apnType = ApnContext.getApnTypeFromNetworkRequest(networkRequest);
-        final ApnContext apnContext = mApnContextsByType.get(apnType);
-        if (apnContext != null) {
-            apnContext.releaseNetwork(networkRequest, type);
-        }
-    }
-
-    // Turn telephony radio on or off.
-    private void setRadio(boolean on) {
-        final ITelephony phone = ITelephony.Stub.asInterface(
-                TelephonyFrameworkInitializer
-                        .getTelephonyServiceManager()
-                        .getTelephonyServiceRegisterer()
-                        .get());
-        try {
-            phone.setRadio(on);
-        } catch (Exception e) {
-            // Ignore.
-        }
-    }
-
-    // Class to handle Intent dispatched with user selects the "Sign-in to network"
-    // notification.
-    private class ProvisionNotificationBroadcastReceiver extends BroadcastReceiver {
-        private final String mNetworkOperator;
-        // Mobile provisioning URL.  Valid while provisioning notification is up.
-        // Set prior to notification being posted as URL contains ICCID which
-        // disappears when radio is off (which is the case when notification is up).
-        private final String mProvisionUrl;
-
-        public ProvisionNotificationBroadcastReceiver(String provisionUrl, String networkOperator) {
-            mNetworkOperator = networkOperator;
-            mProvisionUrl = provisionUrl;
-        }
-
-        private void setEnableFailFastMobileData(int enabled) {
-            sendMessage(obtainMessage(DctConstants.CMD_SET_ENABLE_FAIL_FAST_MOBILE_DATA, enabled, 0));
-        }
-
-        private void enableMobileProvisioning() {
-            final Message msg = obtainMessage(DctConstants.CMD_ENABLE_MOBILE_PROVISIONING);
-            Bundle bundle = new Bundle(1);
-            bundle.putString(DctConstants.PROVISIONING_URL_KEY, mProvisionUrl);
-            msg.setData(bundle);
-            sendMessage(msg);
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (mPhone.getPhoneId() != intent.getIntExtra(EXTRA_PROVISION_PHONE_ID,
-                    SubscriptionManager.INVALID_PHONE_INDEX)) {
-                return;
-            }
-            // Turning back on the radio can take time on the order of a minute, so show user a
-            // spinner so they know something is going on.
-            log("onReceive : ProvisionNotificationBroadcastReceiver");
-            mProvisioningSpinner = new ProgressDialog(context);
-            mProvisioningSpinner.setTitle(mNetworkOperator);
-            mProvisioningSpinner.setMessage(
-                    // TODO: Don't borrow "Connecting..." i18n string; give Telephony a version.
-                    context.getText(com.android.internal.R.string.media_route_status_connecting));
-            mProvisioningSpinner.setIndeterminate(true);
-            mProvisioningSpinner.setCancelable(true);
-            // Allow non-Activity Service Context to create a View.
-            mProvisioningSpinner.getWindow().setType(
-                    WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
-            mProvisioningSpinner.show();
-            // After timeout, hide spinner so user can at least use their device.
-            // TODO: Indicate to user that it is taking an unusually long time to connect?
-            sendMessageDelayed(obtainMessage(DctConstants.CMD_CLEAR_PROVISIONING_SPINNER,
-                    mProvisioningSpinner), PROVISIONING_SPINNER_TIMEOUT_MILLIS);
-            // This code is almost identical to the old
-            // ConnectivityService.handleMobileProvisioningAction code.
-            setRadio(true);
-            setEnableFailFastMobileData(DctConstants.ENABLED);
-            enableMobileProvisioning();
-        }
-    }
-
-    @Override
-    protected void finalize() {
-        if(DBG && mPhone != null) log("finalize");
-    }
-
-    private void initApnContexts() {
-        PersistableBundle carrierConfig;
-        CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
-                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        if (configManager != null) {
-            carrierConfig = configManager.getConfigForSubId(mPhone.getSubId());
-        } else {
-            carrierConfig = null;
-        }
-        initApnContexts(carrierConfig);
-    }
-
-    //Blows away any existing apncontexts that may exist, only use in ctor.
-    private void initApnContexts(PersistableBundle carrierConfig) {
-        if (!mTelephonyManager.isDataCapable()) {
-            log("initApnContexts: isDataCapable == false.  No Apn Contexts loaded");
-            return;
-        }
-
-        log("initApnContexts: E");
-        // Load device network attributes from resources
-        final Collection<ApnConfigType> types =
-                new ApnConfigTypeRepository(carrierConfig).getTypes();
-
-        for (ApnConfigType apnConfigType : types) {
-            ApnContext apnContext = new ApnContext(mPhone, apnConfigType.getType(), mLogTag, this,
-                    apnConfigType.getPriority());
-            int bitmask = ApnSetting.getApnTypesBitmaskFromString(apnContext.getApnType());
-            mPrioritySortedApnContexts.add(apnContext);
-            mApnContexts.put(apnContext.getApnType(), apnContext);
-            mApnContextsByType.put(bitmask, apnContext);
-            // Notify listeners that all data is disconnected when DCT is initialized.
-            // Once connections are established, DC will then notify that data is connected.
-            // This is to prevent the case where the phone process crashed but we don't notify
-            // listeners that data was disconnected, so they may be stuck in a connected state.
-            mPhone.notifyDataConnection(new PreciseDataConnectionState.Builder()
-                    .setTransportType(mTransportType)
-                    .setState(TelephonyManager.DATA_DISCONNECTED)
-                    .setApnSetting(new ApnSetting.Builder()
-                            .setApnTypeBitmask(bitmask).buildWithoutCheck())
-                    .setNetworkType(getDataRat())
-                    .build());
-            log("initApnContexts: apnContext=" + ApnSetting.getApnTypeString(
-                    apnConfigType.getType()));
-        }
-        mPrioritySortedApnContexts.sort((c1, c2) -> c2.getPriority() - c1.getPriority());
-        logSortedApnContexts();
-    }
-
-    private void sortApnContextByPriority() {
-        if (!mTelephonyManager.isDataCapable()) {
-            log("sortApnContextByPriority: isDataCapable == false.  No Apn Contexts loaded");
-            return;
-        }
-
-        PersistableBundle carrierConfig;
-        CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
-                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        if (configManager != null) {
-            carrierConfig = configManager.getConfigForSubId(mPhone.getSubId());
-        } else {
-            carrierConfig = null;
-        }
-
-        log("sortApnContextByPriority: E");
-        // Load device network attributes from resources
-        final Collection<ApnConfigType> types =
-                new ApnConfigTypeRepository(carrierConfig).getTypes();
-        for (ApnConfigType apnConfigType : types) {
-            if (mApnContextsByType.contains(apnConfigType.getType())) {
-                ApnContext apnContext = mApnContextsByType.get(apnConfigType.getType());
-                apnContext.setPriority(apnConfigType.getPriority());
-            }
-        }
-
-        //Doing sorted in a different list to keep thread safety
-        ArrayList<ApnContext> prioritySortedApnContexts =
-                new ArrayList<>(mPrioritySortedApnContexts);
-        prioritySortedApnContexts.sort((c1, c2) -> c2.getPriority() - c1.getPriority());
-        mPrioritySortedApnContexts = prioritySortedApnContexts;
-        logSortedApnContexts();
-    }
-
-    public LinkProperties getLinkProperties(String apnType) {
-        ApnContext apnContext = mApnContexts.get(apnType);
-        if (apnContext != null) {
-            DataConnection dataConnection = apnContext.getDataConnection();
-            if (dataConnection != null) {
-                if (DBG) log("return link properties for " + apnType);
-                return dataConnection.getLinkProperties();
-            }
-        }
-        if (DBG) log("return new LinkProperties");
-        return new LinkProperties();
-    }
-
-    public NetworkCapabilities getNetworkCapabilities(String apnType) {
-        ApnContext apnContext = mApnContexts.get(apnType);
-        if (apnContext!=null) {
-            DataConnection dataConnection = apnContext.getDataConnection();
-            if (dataConnection != null) {
-                if (DBG) {
-                    log("get active pdp is not null, return NetworkCapabilities for " + apnType);
-                }
-                return dataConnection.getNetworkCapabilities();
-            }
-        }
-        if (DBG) log("return new NetworkCapabilities");
-        return new NetworkCapabilities();
-    }
-
-    // Return all active apn types
-    public String[] getActiveApnTypes() {
-        if (DBG) log("get all active apn types");
-        ArrayList<String> result = new ArrayList<String>();
-
-        for (ApnContext apnContext : mApnContexts.values()) {
-            if (mAttached.get() && apnContext.isReady()) {
-                result.add(apnContext.getApnType());
-            }
-        }
-
-        return result.toArray(new String[0]);
-    }
-
-    /**
-     * Get ApnTypes with connected data connections.  This is different than getActiveApnTypes()
-     * which returns apn types that with active apn contexts.
-     * @return apn types
-     */
-    public String[] getConnectedApnTypes() {
-        return mApnContexts.values().stream()
-                .filter(ac -> ac.getState() == DctConstants.State.CONNECTED)
-                .map(ApnContext::getApnType)
-                .toArray(String[]::new);
-    }
-
-    @VisibleForTesting
-    public Collection<ApnContext> getApnContexts() {
-        return mPrioritySortedApnContexts;
-    }
-
-    /** Return active ApnSetting of a specific apnType */
-    public ApnSetting getActiveApnSetting(String apnType) {
-        if (VDBG) log("get active ApnSetting for type:" + apnType);
-        ApnContext apnContext = mApnContexts.get(apnType);
-        return (apnContext != null) ? apnContext.getApnSetting() : null;
-    }
-
-    // Return active apn of specific apn type
-    public String getActiveApnString(String apnType) {
-        if (VDBG) log( "get active apn string for type:" + apnType);
-        ApnSetting setting = getActiveApnSetting(apnType);
-        return (setting != null) ? setting.getApnName() : null;
-    }
-
-    /**
-     * Returns {@link DctConstants.State} based on the state of the {@link DataConnection} that
-     * contains a {@link ApnSetting} that supported the given apn type {@code anpType}.
-     *
-     * <p>
-     * Assumes there is less than one {@link ApnSetting} can support the given apn type.
-     */
-    // TODO: for enterprise this always returns IDLE, which is ok for now since it is never called
-    // for enterprise
-    public DctConstants.State getState(String apnType) {
-        DctConstants.State state = DctConstants.State.IDLE;
-        final int apnTypeBitmask = ApnSetting.getApnTypesBitmaskFromString(apnType);
-        for (DataConnection dc : mDataConnections.values()) {
-            ApnSetting apnSetting = dc.getApnSetting();
-            if (apnSetting != null && apnSetting.canHandleType(apnTypeBitmask)) {
-                if (dc.isActive()) {
-                    state = getBetterConnectionState(state, DctConstants.State.CONNECTED);
-                } else if (dc.isActivating()) {
-                    state = getBetterConnectionState(state, DctConstants.State.CONNECTING);
-                } else if (dc.isInactive()) {
-                    state = getBetterConnectionState(state, DctConstants.State.IDLE);
-                } else if (dc.isDisconnecting()) {
-                    state = getBetterConnectionState(state, DctConstants.State.DISCONNECTING);
-                }
-            }
-        }
-        return state;
-    }
-
-    /**
-     * Return a better connection state between {@code stateA} and {@code stateB}. Check
-     * {@link #DATA_CONNECTION_STATE_PRIORITIES} for the details.
-     * @return the better connection state between {@code stateA} and {@code stateB}.
-     */
-    private static DctConstants.State getBetterConnectionState(
-            DctConstants.State stateA, DctConstants.State stateB) {
-        int idxA = ArrayUtils.indexOf(DATA_CONNECTION_STATE_PRIORITIES, stateA);
-        int idxB = ArrayUtils.indexOf(DATA_CONNECTION_STATE_PRIORITIES, stateB);
-        return idxA >= idxB ? stateA : stateB;
-    }
-
-    // Return if apn type is a provisioning apn.
-    private boolean isProvisioningApn(String apnType) {
-        ApnContext apnContext = mApnContexts.get(apnType);
-        if (apnContext != null) {
-            return apnContext.isProvisioningApn();
-        }
-        return false;
-    }
-
-    //****** Called from ServiceStateTracker
-    /**
-     * Invoked when ServiceStateTracker observes a transition from GPRS
-     * attach to detach.
-     */
-    private void onDataConnectionDetached() {
-        /*
-         * We presently believe it is unnecessary to tear down the PDP context
-         * when GPRS detaches, but we should stop the network polling.
-         */
-        if (DBG) log ("onDataConnectionDetached: stop polling and notify detached");
-        stopNetStatPoll();
-        stopDataStallAlarm();
-        mAttached.set(false);
-    }
-
-    private void onDataConnectionAttached() {
-        if (DBG) log("onDataConnectionAttached");
-        mAttached.set(true);
-        if (isAnyDataConnected()) {
-            if (DBG) log("onDataConnectionAttached: start polling notify attached");
-            startNetStatPoll();
-            startDataStallAlarm(DATA_STALL_NOT_SUSPECTED);
-        }
-        if (mAutoAttachOnCreationConfig) {
-            mAutoAttachEnabled.set(true);
-        }
-        setupDataOnAllConnectableApns(Phone.REASON_DATA_ATTACHED, RetryFailures.ALWAYS);
-    }
-
-    /**
-     * Check if it is allowed to make a data connection (without checking APN context specific
-     * conditions).
-     *
-     * @param dataConnectionReasons Data connection allowed or disallowed reasons as the output
-     *                              param. It's okay to pass null here and no reasons will be
-     *                              provided.
-     * @return True if data connection is allowed, otherwise false.
-     */
-    public boolean isDataAllowed(DataConnectionReasons dataConnectionReasons) {
-        return isDataAllowed(null, REQUEST_TYPE_NORMAL, dataConnectionReasons);
-    }
-
-    /**
-     * Check if it is allowed to make a data connection for a given APN type.
-     *
-     * @param apnContext APN context. If passing null, then will only check general but not APN
-     *                   specific conditions (e.g. APN state, metered/unmetered APN).
-     * @param requestType Setup data request type.
-     * @param dataConnectionReasons Data connection allowed or disallowed reasons as the output
-     *                              param. It's okay to pass null here and no reasons will be
-     *                              provided.
-     * @return True if data connection is allowed, otherwise false.
-     */
-    public boolean isDataAllowed(ApnContext apnContext, @RequestNetworkType int requestType,
-                                 DataConnectionReasons dataConnectionReasons) {
-        // Step 1: Get all environment conditions.
-        // Step 2: Special handling for emergency APN.
-        // Step 3. Build disallowed reasons.
-        // Step 4: Determine if data should be allowed in some special conditions.
-
-        DataConnectionReasons reasons = new DataConnectionReasons();
-
-        int requestApnType = 0;
-        if (apnContext != null) {
-            requestApnType = apnContext.getApnTypeBitmask();
-        }
-
-        // Step 1: Get all environment conditions.
-        final boolean internalDataEnabled = mDataEnabledSettings.isInternalDataEnabled();
-        boolean attachedState = mAttached.get();
-        boolean desiredPowerState = mPhone.getServiceStateTracker().getDesiredPowerState();
-        boolean radioStateFromCarrier = mPhone.getServiceStateTracker().getPowerStateFromCarrier();
-        // TODO: Remove this hack added by ag/641832.
-        int dataRat = getDataRat();
-        if (dataRat == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN) {
-            desiredPowerState = true;
-            radioStateFromCarrier = true;
-        }
-
-        boolean defaultDataSelected = SubscriptionManager.isValidSubscriptionId(
-                SubscriptionManager.getDefaultDataSubscriptionId());
-
-        boolean isMeteredApnType = apnContext == null
-                || ApnSettingUtils.isMeteredApnType(requestApnType, mPhone);
-
-        PhoneConstants.State phoneState = PhoneConstants.State.IDLE;
-        // Note this is explicitly not using mPhone.getState.  See b/19090488.
-        // mPhone.getState reports the merge of CS and PS (volte) voice call state
-        // but we only care about CS calls here for data/voice concurrency issues.
-        // Calling getCallTracker currently gives you just the CS side where the
-        // ImsCallTracker is held internally where applicable.
-        // This should be redesigned to ask explicitly what we want:
-        // voiceCallStateAllowDataCall, or dataCallAllowed or something similar.
-        if (mPhone.getCallTracker() != null) {
-            phoneState = mPhone.getCallTracker().getState();
-        }
-
-        // Step 2: Special handling for emergency APN.
-        if (apnContext != null
-                && requestApnType == ApnSetting.TYPE_EMERGENCY
-                && apnContext.isConnectable()) {
-            // If this is an emergency APN, as long as the APN is connectable, we
-            // should allow it.
-            if (dataConnectionReasons != null) {
-                dataConnectionReasons.add(DataAllowedReasonType.EMERGENCY_APN);
-            }
-            // Bail out without further checks.
-            return true;
-        }
-
-        // Step 3. Build disallowed reasons.
-        if (apnContext != null && !apnContext.isConnectable()) {
-            DctConstants.State state = apnContext.getState();
-            if (state == DctConstants.State.CONNECTED) {
-                reasons.add(DataDisallowedReasonType.DATA_ALREADY_CONNECTED);
-            } else if (state == DctConstants.State.DISCONNECTING) {
-                reasons.add(DataDisallowedReasonType.DATA_IS_DISCONNECTING);
-            } else if (state == DctConstants.State.CONNECTING) {
-                reasons.add(DataDisallowedReasonType.DATA_IS_CONNECTING);
-            } else {
-                reasons.add(DataDisallowedReasonType.APN_NOT_CONNECTABLE);
-            }
-        }
-
-        // In legacy mode, if RAT is IWLAN then don't allow default/IA PDP at all.
-        // Rest of APN types can be evaluated for remaining conditions.
-        if ((apnContext != null && requestApnType == ApnSetting.TYPE_DEFAULT
-                || requestApnType == ApnSetting.TYPE_ENTERPRISE
-                || requestApnType == ApnSetting.TYPE_IA)
-                && mPhone.getAccessNetworksManager().isInLegacyMode()
-                && dataRat == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN) {
-            reasons.add(DataDisallowedReasonType.ON_IWLAN);
-        }
-
-        // If device is not on NR, don't allow enterprise
-        if (apnContext != null && requestApnType == ApnSetting.TYPE_ENTERPRISE
-                && dataRat != ServiceState.RIL_RADIO_TECHNOLOGY_NR) {
-            reasons.add(DataDisallowedReasonType.NOT_ON_NR);
-        }
-
-        if (shouldRestrictDataForEcbm() || mPhone.isInEmergencyCall()) {
-            reasons.add(DataDisallowedReasonType.IN_ECBM);
-        }
-
-        if (!attachedState && !shouldAutoAttach() && requestType != REQUEST_TYPE_HANDOVER) {
-            reasons.add(DataDisallowedReasonType.NOT_ATTACHED);
-        }
-        if (mPhone.getSubId() == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
-            reasons.add(DataDisallowedReasonType.SIM_NOT_READY);
-        }
-        if (phoneState != PhoneConstants.State.IDLE
-                && !mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) {
-            reasons.add(DataDisallowedReasonType.INVALID_PHONE_STATE);
-            reasons.add(DataDisallowedReasonType.CONCURRENT_VOICE_DATA_NOT_ALLOWED);
-        }
-        if (!internalDataEnabled) {
-            reasons.add(DataDisallowedReasonType.INTERNAL_DATA_DISABLED);
-        }
-        if (!defaultDataSelected) {
-            reasons.add(DataDisallowedReasonType.DEFAULT_DATA_UNSELECTED);
-        }
-        if (mPhone.getServiceState().getDataRoaming() && !getDataRoamingEnabled()) {
-            reasons.add(DataDisallowedReasonType.ROAMING_DISABLED);
-        }
-        if (mIsPsRestricted) {
-            reasons.add(DataDisallowedReasonType.PS_RESTRICTED);
-        }
-        if (!desiredPowerState) {
-            reasons.add(DataDisallowedReasonType.UNDESIRED_POWER_STATE);
-        }
-        if (!radioStateFromCarrier) {
-            reasons.add(DataDisallowedReasonType.RADIO_DISABLED_BY_CARRIER);
-        }
-        if (!mDataServiceBound) {
-            reasons.add(DataDisallowedReasonType.DATA_SERVICE_NOT_READY);
-        }
-
-        if (apnContext != null) {
-            if (mPhone.getAccessNetworksManager().getPreferredTransport(
-                    apnContext.getApnTypeBitmask())
-                    == AccessNetworkConstants.TRANSPORT_TYPE_INVALID) {
-                // If QNS explicitly specified this APN type is not allowed on either cellular or
-                // IWLAN, we should not allow data setup.
-                reasons.add(DataDisallowedReasonType.DISABLED_BY_QNS);
-            } else if (mTransportType != mPhone.getAccessNetworksManager().getPreferredTransport(
-                    apnContext.getApnTypeBitmask())) {
-                // If the latest preference has already switched to other transport, we should not
-                // allow data setup.
-                reasons.add(DataDisallowedReasonType.ON_OTHER_TRANSPORT);
-            }
-
-            // If the transport has been already switched to the other transport, we should not
-            // allow the data setup. The only exception is the handover case, where we setup
-            // handover data connection before switching the transport.
-            if (mTransportType != mPhone.getAccessNetworksManager().getCurrentTransport(
-                    apnContext.getApnTypeBitmask()) && requestType != REQUEST_TYPE_HANDOVER) {
-                reasons.add(DataDisallowedReasonType.ON_OTHER_TRANSPORT);
-            }
-
-            // Check if the device is under data throttling.
-            long retryTime = mDataThrottler.getRetryTime(apnContext.getApnTypeBitmask());
-            if (retryTime > SystemClock.elapsedRealtime()) {
-                reasons.add(DataDisallowedReasonType.DATA_THROTTLED);
-            }
-        }
-
-        boolean isDataEnabled = apnContext == null ? mDataEnabledSettings.isDataEnabled()
-                : mDataEnabledSettings.isDataEnabled(requestApnType);
-
-        if (!isDataEnabled) {
-            reasons.add(DataDisallowedReasonType.DATA_DISABLED);
-        }
-
-        // If there are hard disallowed reasons, we should not allow data connection no matter what.
-        if (reasons.containsHardDisallowedReasons()) {
-            if (dataConnectionReasons != null) {
-                dataConnectionReasons.copyFrom(reasons);
-            }
-            return false;
-        }
-
-        // Step 4: Determine if data should be allowed in some special conditions.
-
-        // At this point, if data is not allowed, it must be because of the soft reasons. We
-        // should start to check some special conditions that data will be allowed.
-        if (!reasons.allowed()) {
-            // Check if the transport is WLAN ie wifi (for AP-assisted mode devices)
-            if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
-                reasons.add(DataAllowedReasonType.UNMETERED_APN);
-            // Or if the data is on cellular, and the APN type is determined unmetered by the
-            // configuration.
-            } else if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN
-                    && !isMeteredApnType && requestApnType != ApnSetting.TYPE_DEFAULT
-                    && requestApnType != ApnSetting.TYPE_ENTERPRISE) {
-                reasons.add(DataAllowedReasonType.UNMETERED_APN);
-            }
-
-            // If the request is restricted and there are only soft disallowed reasons (e.g. data
-            // disabled, data roaming disabled) existing, we should allow the data. ENTERPRISE is
-            // an exception and should not be treated as restricted for this purpose; it should be
-            // treated same as DEFAULT.
-            if (apnContext != null
-                    && apnContext.hasRestrictedRequests(true)
-                    && !apnContext.getApnType().equals(ApnSetting.TYPE_ENTERPRISE_STRING)
-                    && !reasons.allowed()) {
-                reasons.add(DataAllowedReasonType.RESTRICTED_REQUEST);
-            }
-        } else {
-            // If there is no disallowed reasons, then we should allow the data request with
-            // normal reason.
-            reasons.add(DataAllowedReasonType.NORMAL);
-        }
-
-        if (dataConnectionReasons != null) {
-            dataConnectionReasons.copyFrom(reasons);
-        }
-
-        return reasons.allowed();
-    }
-
-    // arg for setupDataOnAllConnectableApns
-    protected enum RetryFailures {
-        // retry failed networks always (the old default)
-        ALWAYS,
-        // retry only when a substantial change has occurred.  Either:
-        // 1) we were restricted by voice/data concurrency and aren't anymore
-        // 2) our apn list has change
-        ONLY_ON_CHANGE
-    };
-
-    protected void setupDataOnAllConnectableApns(String reason, RetryFailures retryFailures) {
-        if (VDBG) log("setupDataOnAllConnectableApns: " + reason);
-
-        if (DBG && !VDBG) {
-            StringBuilder sb = new StringBuilder(120);
-            for (ApnContext apnContext : mPrioritySortedApnContexts) {
-                sb.append(apnContext.getApnType());
-                sb.append(":[state=");
-                sb.append(apnContext.getState());
-                sb.append(",enabled=");
-                sb.append(apnContext.isEnabled());
-                sb.append("] ");
-            }
-            log("setupDataOnAllConnectableApns: " + reason + " " + sb);
-        }
-
-        for (ApnContext apnContext : mPrioritySortedApnContexts) {
-            setupDataOnConnectableApn(apnContext, reason, retryFailures);
-        }
-    }
-
-    protected void setupDataOnConnectableApn(ApnContext apnContext, String reason,
-            RetryFailures retryFailures) {
-        if (VDBG) log("setupDataOnAllConnectableApns: apnContext " + apnContext);
-
-        if (apnContext.getState() == DctConstants.State.FAILED
-                || apnContext.getState() == DctConstants.State.RETRYING) {
-            if (retryFailures == RetryFailures.ALWAYS) {
-                apnContext.releaseDataConnection(reason);
-            } else if (!apnContext.isConcurrentVoiceAndDataAllowed()
-                    && mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) {
-                // RetryFailures.ONLY_ON_CHANGE - check if voice concurrency has changed
-                apnContext.releaseDataConnection(reason);
-            }
-        }
-        if (apnContext.isConnectable()) {
-            log("isConnectable() call trySetupData");
-            apnContext.setReason(reason);
-            trySetupData(apnContext, REQUEST_TYPE_NORMAL, null);
-        }
-    }
-
-    private boolean shouldRestrictDataForEcbm() {
-        boolean isInEcm = mPhone.isInEcm();
-        boolean isInImsEcm = mPhone.getImsPhone() != null && mPhone.getImsPhone().isInImsEcm();
-        log("shouldRestrictDataForEcbm: isInEcm=" + isInEcm + " isInImsEcm=" + isInImsEcm);
-        return isInEcm && !isInImsEcm;
-    }
-
-    private boolean isHandoverPending(@ApnType int apnType) {
-        List<Message> messageList = mHandoverCompletionMsgs.get(apnType);
-        return messageList != null && messageList.size() > 0;
-    }
-
-    private void trySetupData(ApnContext apnContext, @RequestNetworkType int requestType,
-            @Nullable Message onHandoverCompleteMsg) {
-        if (onHandoverCompleteMsg != null) {
-            addHandoverCompleteMsg(onHandoverCompleteMsg, apnContext.getApnTypeBitmask());
-        }
-
-        if (mPhone.getSimulatedRadioControl() != null) {
-            // Assume data is connected on the simulator
-            log("trySetupData: X We're on the simulator; assuming connected retValue=true");
-            return;
-        }
-
-        DataConnectionReasons dataConnectionReasons = new DataConnectionReasons();
-        boolean isDataAllowed = isDataAllowed(apnContext, requestType, dataConnectionReasons);
-        String logStr = "trySetupData for APN type " + apnContext.getApnType() + ", reason: "
-                + apnContext.getReason() + ", requestType=" + requestTypeToString(requestType)
-                + ". " + dataConnectionReasons.toString();
-        if (dataConnectionReasons.contains(DataDisallowedReasonType.DISABLED_BY_QNS)
-                || dataConnectionReasons.contains(DataDisallowedReasonType.ON_OTHER_TRANSPORT)) {
-            logStr += ", current transport=" + AccessNetworkConstants.transportTypeToString(
-                    mPhone.getAccessNetworksManager().getCurrentTransport(
-                            apnContext.getApnTypeBitmask()));
-            logStr += ", preferred transport=" + AccessNetworkConstants.transportTypeToString(
-                    mPhone.getAccessNetworksManager().getPreferredTransport(
-                            apnContext.getApnTypeBitmask()));
-        }
-        if (DBG) log(logStr);
-        ApnContext.requestLog(apnContext, logStr);
-        if (!isDataAllowed) {
-            StringBuilder str = new StringBuilder();
-
-            str.append("trySetupData failed. apnContext = [type=" + apnContext.getApnType()
-                    + ", mState=" + apnContext.getState() + ", apnEnabled="
-                    + apnContext.isEnabled() + ", mDependencyMet="
-                    + apnContext.isDependencyMet() + "] ");
-
-            if (!mDataEnabledSettings.isDataEnabled()) {
-                str.append("isDataEnabled() = false. " + mDataEnabledSettings);
-            }
-
-            // Check if it fails because of the existing data is still disconnecting.
-            if (dataConnectionReasons.contains(DataDisallowedReasonType.DATA_IS_DISCONNECTING)
-                    && isHandoverPending(apnContext.getApnTypeBitmask())) {
-                // Normally we don't retry when isDataAllow() returns false, because that's consider
-                // pre-condition not met, for example, data not enabled by the user, or airplane
-                // mode is on. If we retry in those cases, there will be significant power impact.
-                // DATA_IS_DISCONNECTING is a special case we want to retry, and for the handover
-                // case only.
-                log("Data is disconnecting. Will retry handover later.");
-                return;
-            }
-
-            // If this is a data retry, we should set the APN state to FAILED so it won't stay
-            // in RETRYING forever.
-            if (apnContext.getState() == DctConstants.State.RETRYING) {
-                apnContext.setState(DctConstants.State.FAILED);
-                str.append(" Stop retrying.");
-            }
-
-            if (DBG) log(str.toString());
-            ApnContext.requestLog(apnContext, str.toString());
-            if (requestType == REQUEST_TYPE_HANDOVER) {
-                // If fails due to latest preference already changed back to source transport, then
-                // just fallback (will not attempt handover anymore, and will not tear down the
-                // data connection on source transport.
-                boolean fallback = dataConnectionReasons.contains(
-                        DataDisallowedReasonType.ON_OTHER_TRANSPORT);
-                sendHandoverCompleteMessages(apnContext.getApnTypeBitmask(), false, fallback);
-            }
-            return;
-        }
-
-        if (apnContext.getState() == DctConstants.State.FAILED) {
-            String str = "trySetupData: make a FAILED ApnContext IDLE so its reusable";
-            if (DBG) log(str);
-            ApnContext.requestLog(apnContext, str);
-            apnContext.setState(DctConstants.State.IDLE);
-        }
-        int radioTech = getDataRat();
-        if (radioTech == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN && mPhone.getServiceState()
-                .getState() == ServiceState.STATE_IN_SERVICE) {
-            radioTech = getVoiceRat();
-        }
-        log("service state=" + mPhone.getServiceState());
-        apnContext.setConcurrentVoiceAndDataAllowed(mPhone.getServiceStateTracker()
-                .isConcurrentVoiceAndDataAllowed());
-        if (apnContext.getState() == DctConstants.State.IDLE) {
-            ArrayList<ApnSetting> waitingApns =
-                    buildWaitingApns(apnContext.getApnType(), radioTech);
-            if (waitingApns.isEmpty()) {
-                String str = "trySetupData: X No APN found retValue=false";
-                if (DBG) log(str);
-                ApnContext.requestLog(apnContext, str);
-                if (requestType == REQUEST_TYPE_HANDOVER) {
-                    sendHandoverCompleteMessages(apnContext.getApnTypeBitmask(), false,
-                            false);
-                }
-                return;
-            } else {
-                apnContext.setWaitingApns(waitingApns);
-                if (DBG) {
-                    log("trySetupData: Create from mAllApnSettings : "
-                                + apnListToString(mAllApnSettings));
-                }
-            }
-        }
-
-        if (!setupData(apnContext, radioTech, requestType)
-                && requestType == REQUEST_TYPE_HANDOVER) {
-            sendHandoverCompleteMessages(apnContext.getApnTypeBitmask(), false, false);
-        }
-    }
-
-    /**
-     * Clean up all data connections. Note this is just detach the APN context from the data
-     * connection. After all APN contexts are detached from the data connection, the data
-     * connection will be torn down.
-     *
-     * @param reason Reason for the clean up.
-     */
-    public void cleanUpAllConnections(String reason) {
-        log("cleanUpAllConnections");
-        Message msg = obtainMessage(DctConstants.EVENT_CLEAN_UP_ALL_CONNECTIONS);
-        msg.obj = reason;
-        sendMessage(msg);
-    }
-
-    /**
-     * Clean up all data connections by detaching the APN contexts from the data connections, which
-     * eventually tearing down all data connections after all APN contexts are detached from the
-     * data connections.
-     *
-     * @param detach {@code true} if detaching APN context from the underlying data connection (when
-     * no other APN context is attached to the data connection, the data connection will be torn
-     * down.) {@code false} to only reset the data connection's state machine.
-     *
-     * @param reason reason for the clean up.
-     * @return boolean - true if we did cleanup any connections, false if they
-     *                   were already all disconnected.
-     */
-    private boolean cleanUpAllConnectionsInternal(boolean detach, String reason) {
-        if (DBG) log("cleanUpAllConnectionsInternal: detach=" + detach + " reason=" + reason);
-        boolean didDisconnect = false;
-        boolean disableMeteredOnly = false;
-
-        // reasons that only metered apn will be torn down
-        if (!TextUtils.isEmpty(reason)) {
-            disableMeteredOnly = reason.equals(Phone.REASON_DATA_SPECIFIC_DISABLED) ||
-                    reason.equals(Phone.REASON_ROAMING_ON) ||
-                    reason.equals(Phone.REASON_CARRIER_ACTION_DISABLE_METERED_APN);
-        }
-
-        for (ApnContext apnContext : mApnContexts.values()) {
-            // Exclude the IMS APN from single data connection case.
-            if (reason.equals(Phone.REASON_SINGLE_PDN_ARBITRATION)
-                    && apnContext.getApnType().equals(ApnSetting.TYPE_IMS_STRING)) {
-                continue;
-            }
-
-            if (shouldCleanUpConnection(apnContext, disableMeteredOnly,
-                    reason.equals(Phone.REASON_SINGLE_PDN_ARBITRATION))) {
-                // TODO - only do cleanup if not disconnected
-                if (apnContext.isDisconnected() == false) didDisconnect = true;
-                apnContext.setReason(reason);
-                cleanUpConnectionInternal(detach, RELEASE_TYPE_DETACH, apnContext);
-            } else if (DBG) {
-                log("cleanUpAllConnectionsInternal: APN type " + apnContext.getApnType()
-                        + " shouldn't be cleaned up.");
-            }
-        }
-
-        stopNetStatPoll();
-        stopDataStallAlarm();
-
-        // TODO: Do we need mRequestedApnType?
-        mRequestedApnType = ApnSetting.TYPE_DEFAULT;
-
-        if (areAllDataDisconnected()) {
-            notifyAllDataDisconnected();
-        }
-
-        return didDisconnect;
-    }
-
-    boolean shouldCleanUpConnection(ApnContext apnContext, boolean disableMeteredOnly,
-            boolean singlePdn) {
-        if (apnContext == null) return false;
-
-        // If APN setting is not null and the reason is single PDN arbitration, clean up connection.
-        ApnSetting apnSetting = apnContext.getApnSetting();
-        if (apnSetting != null && singlePdn) return true;
-
-        // If meteredOnly is false, clean up all connections.
-        if (!disableMeteredOnly) return true;
-
-        // If meteredOnly is true, and apnSetting is null or it's un-metered, no need to clean up.
-        if (apnSetting == null || !ApnSettingUtils.isMetered(apnSetting, mPhone)) return false;
-
-        boolean isRoaming = mPhone.getServiceState().getDataRoaming();
-        boolean isDataRoamingDisabled = !getDataRoamingEnabled();
-        boolean isDataDisabled = !mDataEnabledSettings.isDataEnabled(
-                apnSetting.getApnTypeBitmask());
-
-        // Should clean up if its data is disabled, or data roaming is disabled while roaming.
-        return isDataDisabled || (isRoaming && isDataRoamingDisabled);
-    }
-
-    /**
-     * Detach the APN context from the associated data connection. This data connection might be
-     * torn down if no other APN context is attached to it.
-     *
-     * @param apnContext The APN context to be detached
-     */
-    void cleanUpConnection(ApnContext apnContext) {
-        if (DBG) log("cleanUpConnection: apnContext=" + apnContext);
-        Message msg = obtainMessage(DctConstants.EVENT_CLEAN_UP_CONNECTION);
-        msg.arg2 = 0;
-        msg.obj = apnContext;
-        sendMessage(msg);
-    }
-
-    /**
-     * Detach the APN context from the associated data connection. This data connection will be
-     * torn down if no other APN context is attached to it.
-     *
-     * @param detach {@code true} if detaching APN context from the underlying data connection (when
-     * no other APN context is attached to the data connection, the data connection will be torn
-     * down.) {@code false} to only reset the data connection's state machine.
-     * @param releaseType Data release type.
-     * @param apnContext The APN context to be detached.
-     */
-    private void cleanUpConnectionInternal(boolean detach, @ReleaseNetworkType int releaseType,
-                                           ApnContext apnContext) {
-        if (apnContext == null) {
-            if (DBG) log("cleanUpConnectionInternal: apn context is null");
-            return;
-        }
-
-        DataConnection dataConnection = apnContext.getDataConnection();
-        String str = "cleanUpConnectionInternal: detach=" + detach + " reason="
-                + apnContext.getReason();
-        if (VDBG) log(str + " apnContext=" + apnContext);
-        ApnContext.requestLog(apnContext, str);
-        if (detach) {
-            if (apnContext.isDisconnected()) {
-                // The request is detach and but ApnContext is not connected.
-                // If apnContext is not enabled anymore, break the linkage to the data connection.
-                apnContext.releaseDataConnection("");
-            } else {
-                // Connection is still there. Try to clean up.
-                if (dataConnection != null) {
-                    if (apnContext.getState() != DctConstants.State.DISCONNECTING) {
-                        boolean disconnectAll = false;
-                        if (ApnSetting.TYPE_DUN_STRING.equals(apnContext.getApnType())
-                                && ServiceState.isCdma(getDataRat())) {
-                            if (DBG) {
-                                log("cleanUpConnectionInternal: disconnectAll DUN connection");
-                            }
-                            // For CDMA DUN, we need to tear it down immediately. A new data
-                            // connection will be reestablished with correct profile id.
-                            disconnectAll = true;
-                        }
-                        final int generation = apnContext.getConnectionGeneration();
-                        str = "cleanUpConnectionInternal: tearing down"
-                                + (disconnectAll ? " all" : "") + " using gen#" + generation;
-                        if (DBG) log(str + "apnContext=" + apnContext);
-                        ApnContext.requestLog(apnContext, str);
-                        Pair<ApnContext, Integer> pair = new Pair<>(apnContext, generation);
-                        Message msg = obtainMessage(DctConstants.EVENT_DISCONNECT_DONE, pair);
-
-                        if (disconnectAll || releaseType == RELEASE_TYPE_HANDOVER) {
-                            dataConnection.tearDownAll(apnContext.getReason(), releaseType, msg);
-                        } else {
-                            dataConnection.tearDown(apnContext, apnContext.getReason(), msg);
-                        }
-
-                        apnContext.setState(DctConstants.State.DISCONNECTING);
-                    }
-                } else {
-                    // apn is connected but no reference to the data connection.
-                    // Should not be happen, but reset the state in case.
-                    apnContext.setState(DctConstants.State.IDLE);
-                    ApnContext.requestLog(
-                            apnContext, "cleanUpConnectionInternal: connected, bug no dc");
-                }
-            }
-        } else {
-            // force clean up the data connection.
-            if (dataConnection != null) dataConnection.reset();
-            apnContext.setState(DctConstants.State.IDLE);
-            apnContext.setDataConnection(null);
-        }
-
-        // If there is any outstanding handover request, we need to respond it.
-        sendHandoverCompleteMessages(apnContext.getApnTypeBitmask(), false, false);
-
-        // Make sure reconnection alarm is cleaned up if there is no ApnContext
-        // associated to the connection.
-        if (dataConnection != null) {
-            cancelReconnect(apnContext);
-        }
-        str = "cleanUpConnectionInternal: X detach=" + detach + " reason="
-                + apnContext.getReason();
-        if (DBG) log(str + " apnContext=" + apnContext + " dc=" + apnContext.getDataConnection());
-    }
-
-    private Cursor getPreferredApnCursor(int subId) {
-        Cursor cursor = null;
-        if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
-            cursor = mPhone.getContext().getContentResolver().query(
-                    Uri.withAppendedPath(PREFERAPN_NO_UPDATE_URI_USING_SUBID,
-                            String.valueOf(subId)), null, null, null,
-                    Telephony.Carriers.DEFAULT_SORT_ORDER);
-        }
-        return cursor;
-    }
-
-    private ApnSetting getPreferredApnFromDB() {
-        ApnSetting preferredApn = null;
-        Cursor cursor = getPreferredApnCursor(mPhone.getSubId());
-        if (cursor != null) {
-            if (cursor.getCount() > 0) {
-                cursor.moveToFirst();
-                preferredApn = ApnSetting.makeApnSetting(cursor);
-            }
-            cursor.close();
-        }
-        if (VDBG) log("getPreferredApnFromDB: preferredApn=" + preferredApn);
-        return preferredApn;
-    }
-
-    private void setDefaultPreferredApnIfNeeded() {
-        ApnSetting defaultPreferredApn = null;
-        PersistableBundle bundle = getCarrierConfig();
-        String defaultPreferredApnName = bundle.getString(CarrierConfigManager
-                .KEY_DEFAULT_PREFERRED_APN_NAME_STRING);
-
-        if (TextUtils.isEmpty(defaultPreferredApnName) || getPreferredApnFromDB() != null) {
-            return;
-        }
-
-        String selection = Telephony.Carriers.APN + " = \"" + defaultPreferredApnName + "\" AND "
-                + Telephony.Carriers.EDITED_STATUS + " = " + Telephony.Carriers.UNEDITED;
-        Cursor cursor = mPhone.getContext().getContentResolver().query(
-                Uri.withAppendedPath(Telephony.Carriers.SIM_APN_URI,
-                        "filtered/subId/" + mPhone.getSubId()),
-                null, selection, null, Telephony.Carriers._ID);
-
-        if (cursor != null) {
-            if (cursor.getCount() > 0) {
-                if (cursor.moveToFirst()) {
-                    defaultPreferredApn = ApnSetting.makeApnSetting(cursor);
-                }
-            }
-            cursor.close();
-        }
-
-        if (defaultPreferredApn != null
-                && defaultPreferredApn.canHandleType(mRequestedApnType)) {
-            log("setDefaultPreferredApnIfNeeded: For APN type "
-                    + ApnSetting.getApnTypeString(mRequestedApnType)
-                    + " found default apnSetting "
-                    + defaultPreferredApn);
-
-            setPreferredApn(defaultPreferredApn.getId(), true);
-        }
-
-        return;
-    }
-
-    /**
-     * Check if preferred apn is allowed to edit by user.
-     * @return {@code true} if it is allowed to edit.
-     */
-    @VisibleForTesting
-    public boolean isPreferredApnUserEdited() {
-        boolean isUserEdited = false;
-        Cursor cursor = getPreferredApnCursor(mPhone.getSubId());
-        if (cursor != null) {
-            if (cursor.getCount() > 0) {
-                if (cursor.moveToFirst()) {
-                    isUserEdited = cursor.getInt(
-                            cursor.getColumnIndexOrThrow(Telephony.Carriers.EDITED_STATUS))
-                            == Telephony.Carriers.USER_EDITED;
-                }
-            }
-            cursor.close();
-        }
-        if (VDBG) log("isPreferredApnUserEdited: isUserEdited=" + isUserEdited);
-        return isUserEdited;
-    }
-
-    /**
-     * Fetch the DUN apns
-     * @return a list of DUN ApnSetting objects
-     */
-    @VisibleForTesting
-    public @NonNull ArrayList<ApnSetting> fetchDunApns() {
-        if (mPhone.getServiceState().getRoaming() && !isPreferredApnUserEdited()
-                && getCarrierConfig().getBoolean(CarrierConfigManager
-                .KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL)) {
-            if (VDBG) log("fetchDunApns: Dun apn is not used in roaming network");
-            return new ArrayList<ApnSetting>(0);
-        }
-
-        int bearer = getDataRat();
-        ArrayList<ApnSetting> dunCandidates = new ArrayList<ApnSetting>();
-        ArrayList<ApnSetting> retDunSettings = new ArrayList<ApnSetting>();
-
-        if (dunCandidates.isEmpty()) {
-            if (!ArrayUtils.isEmpty(mAllApnSettings)) {
-                for (ApnSetting apn : mAllApnSettings) {
-                    if (apn.canHandleType(ApnSetting.TYPE_DUN)) {
-                        dunCandidates.add(apn);
-                    }
-                }
-                if (VDBG) log("fetchDunApns: dunCandidates from database: " + dunCandidates);
-            }
-        }
-
-        int preferredApnSetId = getPreferredApnSetId();
-        ApnSetting preferredApn = getPreferredApnFromDB();
-        for (ApnSetting dunSetting : dunCandidates) {
-            if (dunSetting.canSupportNetworkType(
-                    ServiceState.rilRadioTechnologyToNetworkType(bearer))) {
-                if (preferredApnSetId == dunSetting.getApnSetId()) {
-                    if (preferredApn != null && preferredApn.equals(dunSetting)) {
-                        // If there is a preferred APN can handled DUN type, prepend it to list to
-                        // use it preferred.
-                        retDunSettings.add(0, dunSetting);
-                    } else {
-                        retDunSettings.add(dunSetting);
-                    }
-                }
-            }
-        }
-
-        if (VDBG) log("fetchDunApns: dunSettings=" + retDunSettings);
-        return retDunSettings;
-    }
-
-    private int getPreferredApnSetId() {
-        // preferapnset uri returns all APNs for the current carrier which have an apn_set_id
-        // equal to the preferred APN (if no preferred APN, or if the preferred APN has no set id,
-        // the query will return null)
-        Cursor c = mPhone.getContext().getContentResolver()
-                .query(Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI,
-                    "preferapnset/subId/" + mPhone.getSubId()),
-                        new String[] {Telephony.Carriers.APN_SET_ID}, null, null, null);
-        if (c == null) {
-            loge("getPreferredApnSetId: cursor is null");
-            return Telephony.Carriers.NO_APN_SET_ID;
-        }
-
-        int setId;
-        if (c.getCount() < 1) {
-            loge("getPreferredApnSetId: no APNs found");
-            setId = Telephony.Carriers.NO_APN_SET_ID;
-        } else {
-            c.moveToFirst();
-            setId = c.getInt(0 /* index of Telephony.Carriers.APN_SET_ID */);
-        }
-
-        if (!c.isClosed()) {
-            c.close();
-        }
-        return setId;
-    }
-
-    public boolean hasMatchedTetherApnSetting() {
-        ArrayList<ApnSetting> matches = fetchDunApns();
-        log("hasMatchedTetherApnSetting: APNs=" + matches);
-        return matches.size() > 0;
-    }
-
-    /**
-     * @return the {@link DataConnection} with the given context id {@code cid}.
-     */
-    public DataConnection getDataConnectionByContextId(int cid) {
-        return mDcc.getActiveDcByCid(cid);
-    }
-
-    /**
-     * @return the {@link DataConnection} with the given APN context. Null if no data connection
-     * is found.
-     */
-    public @Nullable DataConnection getDataConnectionByApnType(String apnType) {
-        // TODO: Clean up all APN type in string usage
-        ApnContext apnContext = mApnContexts.get(apnType);
-        if (apnContext != null) {
-            return apnContext.getDataConnection();
-        }
-        return null;
-    }
-
-    /**
-     * Check if the data fail cause is a permanent failure (i.e. Frameworks will not retry data
-     * setup).
-     *
-     * @param dcFailCause The data fail cause
-     * @return {@code true} if the data fail cause is a permanent failure.
-     */
-    @VisibleForTesting
-    public boolean isPermanentFailure(@DataFailureCause int dcFailCause) {
-        return (DataFailCause.isPermanentFailure(mPhone.getContext(), dcFailCause,
-                mPhone.getSubId())
-                && (mAttached.get() == false || dcFailCause != DataFailCause.SIGNAL_LOST));
-    }
-
-    private DataConnection findFreeDataConnection() {
-        for (DataConnection dataConnection : mDataConnections.values()) {
-            boolean inUse = false;
-            for (ApnContext apnContext : mApnContexts.values()) {
-                if (apnContext.getDataConnection() == dataConnection) {
-                    inUse = true;
-                    break;
-                }
-            }
-            if (!inUse) {
-                if (DBG) {
-                    log("findFreeDataConnection: found free DataConnection=" + dataConnection);
-                }
-                return dataConnection;
-            }
-        }
-        log("findFreeDataConnection: NO free DataConnection");
-        return null;
-    }
-
-    /**
-     * Setup a data connection based on given APN type.
-     *
-     * @param apnContext APN context
-     * @param radioTech RAT of the data connection
-     * @param requestType Data request type
-     * @return True if successful, otherwise false.
-     */
-    private boolean setupData(ApnContext apnContext, int radioTech,
-                              @RequestNetworkType int requestType) {
-        if (DBG) {
-            log("setupData: apnContext=" + apnContext + ", requestType="
-                    + requestTypeToString(requestType));
-        }
-        ApnContext.requestLog(
-                apnContext, "setupData. requestType=" + requestTypeToString(requestType));
-        ApnSetting apnSetting;
-        DataConnection dataConnection = null;
-
-        apnSetting = apnContext.getNextApnSetting();
-
-        if (apnSetting == null) {
-            if (DBG) log("setupData: return for no apn found!");
-            return false;
-        }
-
-        // profile id is only meaningful when the profile is persistent on the modem.
-        int profileId = DATA_PROFILE_INVALID;
-        if (apnSetting.isPersistent()) {
-            profileId = apnSetting.getProfileId();
-            if (profileId == DATA_PROFILE_DEFAULT) {
-                profileId = getApnProfileID(apnContext.getApnType());
-            }
-        }
-
-        // On CDMA, if we're explicitly asking for DUN, we need have
-        // a dun-profiled connection so we can't share an existing one
-        // On GSM/LTE we can share existing apn connections provided they support
-        // this type.
-        // If asking for ENTERPRISE, there are no compatible data connections, so skip this check
-        if ((apnContext.getApnTypeBitmask() != ApnSetting.TYPE_DUN
-                || ServiceState.isGsm(getDataRat()))
-                && apnContext.getApnTypeBitmask() != ApnSetting.TYPE_ENTERPRISE) {
-            dataConnection = checkForCompatibleDataConnection(apnContext, apnSetting);
-            if (dataConnection != null) {
-                // Get the apn setting used by the data connection
-                ApnSetting dataConnectionApnSetting = dataConnection.getApnSetting();
-                if (dataConnectionApnSetting != null) {
-                    // Setting is good, so use it.
-                    apnSetting = dataConnectionApnSetting;
-                }
-            }
-        }
-        if (dataConnection == null) {
-            if (isOnlySingleDcAllowed(radioTech)) {
-                if (isHigherPriorityApnContextActive(apnContext)) {
-                    if (DBG) {
-                        log("setupData: Higher priority ApnContext active.  Ignoring call");
-                    }
-                    return false;
-                }
-
-                // Should not start cleanUp if the setupData is for IMS APN
-                // or retry of same APN(State==RETRYING).
-                if (!apnContext.getApnType().equals(ApnSetting.TYPE_IMS_STRING)
-                        && (apnContext.getState() != DctConstants.State.RETRYING)) {
-                    // Only lower priority calls left.  Disconnect them all in this single PDP case
-                    // so that we can bring up the requested higher priority call (once we receive
-                    // response for deactivate request for the calls we are about to disconnect
-                    if (cleanUpAllConnectionsInternal(true, Phone.REASON_SINGLE_PDN_ARBITRATION)) {
-                        // If any call actually requested to be disconnected, means we can't
-                        // bring up this connection yet as we need to wait for those data calls
-                        // to be disconnected.
-                        if (DBG) log("setupData: Some calls are disconnecting first."
-                                + " Wait and retry");
-                        return false;
-                    }
-                }
-
-                // No other calls are active, so proceed
-                if (DBG) log("setupData: Single pdp. Continue setting up data call.");
-            }
-
-            dataConnection = findFreeDataConnection();
-
-            if (dataConnection == null) {
-                dataConnection = createDataConnection();
-            }
-
-            if (dataConnection == null) {
-                if (DBG) log("setupData: No free DataConnection and couldn't create one, WEIRD");
-                return false;
-            }
-        }
-        final int generation = apnContext.incAndGetConnectionGeneration();
-        if (DBG) {
-            log("setupData: dc=" + dataConnection + " apnSetting=" + apnSetting + " gen#="
-                    + generation);
-        }
-
-        apnContext.setDataConnection(dataConnection);
-        apnContext.setApnSetting(apnSetting);
-        apnContext.setState(DctConstants.State.CONNECTING);
-
-        Message msg = obtainMessage();
-        msg.what = DctConstants.EVENT_DATA_SETUP_COMPLETE;
-        msg.obj = new Pair<ApnContext, Integer>(apnContext, generation);
-
-        ApnSetting preferredApn = getPreferredApn();
-        boolean isPreferredApn = apnSetting.equals(preferredApn);
-        dataConnection.bringUp(apnContext, profileId, radioTech, msg, generation, requestType,
-                mPhone.getSubId(), isPreferredApn);
-
-        if (DBG) {
-            if (isPreferredApn) {
-                log("setupData: initing! isPreferredApn=" + isPreferredApn
-                        + ", apnSetting={" + apnSetting.toString() + "}");
-            } else {
-                String preferredApnStr = preferredApn == null ? "null" : preferredApn.toString();
-                log("setupData: initing! isPreferredApn=" + isPreferredApn
-                        + ", apnSetting={" + apnSetting + "}"
-                        + ", preferredApn={" + preferredApnStr + "}");
-            }
-        }
-        return true;
-    }
-
-    // Get the allowed APN types for initial attach. The order in the returned list represent
-    // the order of APN types that should be used for initial attach.
-    private @NonNull @ApnType List<Integer> getAllowedInitialAttachApnTypes() {
-        PersistableBundle bundle = getCarrierConfig();
-        if (bundle != null) {
-            String[] apnTypesArray = bundle.getStringArray(
-                    CarrierConfigManager.KEY_ALLOWED_INITIAL_ATTACH_APN_TYPES_STRING_ARRAY);
-            if (apnTypesArray != null) {
-                return Arrays.stream(apnTypesArray)
-                        .map(ApnSetting::getApnTypesBitmaskFromString)
-                        .collect(Collectors.toList());
-            }
-        }
-
-        return Collections.emptyList();
-    }
-
-    protected void setInitialAttachApn() {
-        ApnSetting apnSetting = null;
-        int preferredApnSetId = getPreferredApnSetId();
-        ArrayList<ApnSetting> allApnSettings = new ArrayList<>();
-        if (mPreferredApn != null) {
-            // Put the preferred apn at the beginning of the list. It's okay to have a duplicate
-            // when later on mAllApnSettings get added. That would not change the selection result.
-            allApnSettings.add(mPreferredApn);
-        }
-        allApnSettings.addAll(mAllApnSettings);
-
-        // Get the allowed APN types for initial attach. Note that if none of the APNs has the
-        // allowed APN types, then the initial attach will not be performed.
-        List<Integer> allowedApnTypes = getAllowedInitialAttachApnTypes();
-        for (int allowedApnType : allowedApnTypes) {
-            apnSetting = allApnSettings.stream()
-                    .filter(apn -> apn.canHandleType(allowedApnType))
-                    .filter(apn -> (apn.getApnSetId() == preferredApnSetId
-                            || apn.getApnSetId() == Telephony.Carriers.MATCH_ALL_APN_SET_ID))
-                    .findFirst()
-                    .orElse(null);
-            if (apnSetting != null) break;
-        }
-
-        if (DBG) {
-            log("setInitialAttachApn: Allowed APN types=" + allowedApnTypes.stream()
-                    .map(ApnSetting::getApnTypeString)
-                    .collect(Collectors.joining(",")));
-        }
-
-        if (apnSetting == null) {
-            if (DBG) log("setInitialAttachApn: X There in no available apn.");
-        } else {
-            if (DBG) log("setInitialAttachApn: X selected APN=" + apnSetting);
-            mDataServiceManager.setInitialAttachApn(new DataProfile.Builder()
-                    .setApnSetting(apnSetting)
-                    .setPreferred(apnSetting.equals(getPreferredApn()))
-                    .build(),
-                    mPhone.getServiceState().getDataRoamingFromRegistration(), null);
-        }
-    }
-
-    /**
-     * Handles changes to the APN database.
-     */
-    private void onApnChanged() {
-        if (mPhone instanceof GsmCdmaPhone) {
-            // The "current" may no longer be valid.  MMS depends on this to send properly. TBD
-            ((GsmCdmaPhone)mPhone).updateCurrentCarrierInProvider();
-        }
-
-        // TODO: It'd be nice to only do this if the changed entrie(s)
-        // match the current operator.
-        if (DBG) log("onApnChanged: createAllApnList and cleanUpAllConnections");
-        mDataThrottler.reset();
-        setDefaultPreferredApnIfNeeded();
-        createAllApnList();
-        setDataProfilesAsNeeded();
-        setInitialAttachApn();
-        cleanUpConnectionsOnUpdatedApns(isAnyDataConnected(), Phone.REASON_APN_CHANGED);
-
-        // FIXME: See bug 17426028 maybe no conditional is needed.
-        if (mPhone.getSubId() == SubscriptionManager.getDefaultDataSubscriptionId()) {
-            setupDataOnAllConnectableApns(Phone.REASON_APN_CHANGED, RetryFailures.ALWAYS);
-        }
-    }
-
-    /**
-     * "Active" here means ApnContext isEnabled() and not in FAILED state
-     * @param apnContext to compare with
-     * @return true if higher priority active apn found
-     */
-    private boolean isHigherPriorityApnContextActive(ApnContext apnContext) {
-        if (apnContext.getApnType().equals(ApnSetting.TYPE_IMS_STRING)) {
-            return false;
-        }
-
-        for (ApnContext otherContext : mPrioritySortedApnContexts) {
-            if (otherContext.getApnType().equals(ApnSetting.TYPE_IMS_STRING)) {
-                continue;
-            }
-            if (apnContext.getApnType().equalsIgnoreCase(otherContext.getApnType())) return false;
-            if (otherContext.isEnabled() && otherContext.getState() != DctConstants.State.FAILED) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Reports if we support multiple connections or not.
-     * This is a combination of factors, based on carrier and RAT.
-     * @param rilRadioTech the RIL Radio Tech currently in use
-     * @return true if only single DataConnection is allowed
-     */
-    private boolean isOnlySingleDcAllowed(int rilRadioTech) {
-        int networkType = ServiceState.rilRadioTechnologyToNetworkType(rilRadioTech);
-        // Default single dc rats with no knowledge of carrier
-        int[] singleDcRats = null;
-        // get the carrier specific value, if it exists, from CarrierConfigManager.
-        // generally configManager and bundle should not be null, but if they are it should be okay
-        // to leave singleDcRats null as well
-        CarrierConfigManager configManager = (CarrierConfigManager)
-                mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        if (configManager != null) {
-            PersistableBundle bundle = configManager.getConfigForSubId(mPhone.getSubId());
-            if (bundle != null) {
-                singleDcRats = bundle.getIntArray(
-                        CarrierConfigManager.KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY);
-            }
-        }
-        boolean onlySingleDcAllowed = false;
-        if (TelephonyUtils.IS_DEBUGGABLE
-                && SystemProperties.getBoolean("persist.telephony.test.singleDc", false)) {
-            onlySingleDcAllowed = true;
-        }
-        if (singleDcRats != null) {
-            for (int i = 0; i < singleDcRats.length && !onlySingleDcAllowed; i++) {
-                if (networkType == singleDcRats[i]) {
-                    onlySingleDcAllowed = true;
-                }
-            }
-        }
-
-        if (DBG) {
-            log("isOnlySingleDcAllowed(" + TelephonyManager.getNetworkTypeName(networkType) + "): "
-                    + onlySingleDcAllowed);
-        }
-        return onlySingleDcAllowed;
-    }
-
-    void sendRestartRadio() {
-        if (DBG)log("sendRestartRadio:");
-        Message msg = obtainMessage(DctConstants.EVENT_RESTART_RADIO);
-        sendMessage(msg);
-    }
-
-    private void restartRadio() {
-        if (DBG) log("restartRadio: ************TURN OFF RADIO**************");
-        cleanUpAllConnectionsInternal(true, Phone.REASON_RADIO_TURNED_OFF);
-        mPhone.getServiceStateTracker().powerOffRadioSafely();
-        /* Note: no need to call setRadioPower(true).  Assuming the desired
-         * radio power state is still ON (as tracked by ServiceStateTracker),
-         * ServiceStateTracker will call setRadioPower when it receives the
-         * RADIO_STATE_CHANGED notification for the power off.  And if the
-         * desired power state has changed in the interim, we don't want to
-         * override it with an unconditional power on.
-         */
-    }
-
-    /**
-     * Return true if data connection need to be setup after disconnected due to
-     * reason.
-     *
-     * @param apnContext APN context
-     * @return true if try setup data connection is need for this reason
-     */
-    private boolean retryAfterDisconnected(ApnContext apnContext) {
-        boolean retry = true;
-        String reason = apnContext.getReason();
-
-        if (Phone.REASON_RADIO_TURNED_OFF.equals(reason) || (isOnlySingleDcAllowed(getDataRat())
-                && isHigherPriorityApnContextActive(apnContext))) {
-            retry = false;
-        }
-        return retry;
-    }
-
-    protected void startReconnect(long delay, ApnContext apnContext,
-            @RequestNetworkType int requestType) {
-        apnContext.setState(DctConstants.State.RETRYING);
-        Message msg = obtainMessage(DctConstants.EVENT_DATA_RECONNECT,
-                       mPhone.getSubId(), requestType, apnContext);
-        cancelReconnect(apnContext);
-
-        // Wait a bit before trying the next APN, so that
-        // we're not tying up the RIL command channel
-        sendMessageDelayed(msg, delay);
-
-        if (DBG) {
-            log("startReconnect: delay=" + delay + ", apn="
-                    + apnContext + ", reason=" + apnContext.getReason()
-                    + ", subId=" + mPhone.getSubId() + ", request type="
-                    + requestTypeToString(requestType));
-        }
-    }
-
-    /**
-     * Cancels the alarm associated with apnContext.
-     *
-     * @param apnContext on which the alarm should be stopped.
-     */
-    protected void cancelReconnect(ApnContext apnContext) {
-        if (apnContext == null) return;
-
-        if (DBG) {
-            log("cancelReconnect: apn=" + apnContext);
-        }
-        removeMessages(DctConstants.EVENT_DATA_RECONNECT, apnContext);
-    }
-
-    /**
-     * Read configuration. Note this must be called after carrier config is ready.
-     */
-    private void readConfiguration() {
-        log("readConfiguration");
-        if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
-            // Auto attach is for cellular only.
-            mAutoAttachOnCreationConfig = mPhone.getContext().getResources()
-                    .getBoolean(com.android.internal.R.bool.config_auto_attach_data_on_creation);
-        }
-
-        mAutoAttachEnabled.set(false);
-        setDefaultPreferredApnIfNeeded();
-        read5GConfiguration();
-        registerSettingsObserver();
-        SubscriptionPlan[] plans = mNetworkPolicyManager.getSubscriptionPlans(
-                mPhone.getSubId(), mPhone.getContext().getOpPackageName());
-        mSubscriptionPlans = plans == null ? Collections.emptyList() : Arrays.asList(plans);
-        if (DBG) log("SubscriptionPlans initialized: " + mSubscriptionPlans);
-        reevaluateUnmeteredConnections();
-        mConfigReady = true;
-    }
-
-    /**
-     * @return {@code true} if carrier config has been applied.
-     */
-    private boolean isCarrierConfigApplied() {
-        CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
-                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        if (configManager != null) {
-            PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId());
-            if (b != null) {
-                return CarrierConfigManager.isConfigForIdentifiedCarrier(b);
-            }
-        }
-        return false;
-    }
-
-    private void onCarrierConfigChanged() {
-        if (DBG) log("onCarrierConfigChanged");
-
-        if (!isCarrierConfigApplied()) {
-            log("onCarrierConfigChanged: Carrier config is not ready yet.");
-            return;
-        }
-
-        readConfiguration();
-
-        if (mSimState == TelephonyManager.SIM_STATE_LOADED) {
-            setDefaultDataRoamingEnabled();
-            createAllApnList();
-            setDataProfilesAsNeeded();
-            setInitialAttachApn();
-            sortApnContextByPriority();
-            cleanUpConnectionsOnUpdatedApns(true, Phone.REASON_CARRIER_CHANGE);
-            setupDataOnAllConnectableApns(Phone.REASON_CARRIER_CHANGE, RetryFailures.ALWAYS);
-        } else {
-            log("onCarrierConfigChanged: SIM is not loaded yet.");
-        }
-    }
-
-    private void onSimAbsent() {
-        if (DBG) log("onSimAbsent");
-
-        mConfigReady = false;
-        cleanUpAllConnectionsInternal(true, Phone.REASON_SIM_NOT_READY);
-        mAllApnSettings.clear();
-        mAutoAttachOnCreationConfig = false;
-        // Clear auto attach as modem is expected to do a new attach once SIM is ready
-        mAutoAttachEnabled.set(false);
-        // In no-sim case, we should still send the emergency APN to the modem, if there is any.
-        createAllApnList();
-        setDataProfilesAsNeeded();
-    }
-
-    private void onSimStateUpdated(@SimState int simState) {
-        mSimState = simState;
-
-        if (DBG) {
-            log("onSimStateUpdated: state=" + SubscriptionInfoUpdater.simStateString(mSimState));
-        }
-
-        if (mSimState == TelephonyManager.SIM_STATE_ABSENT) {
-            onSimAbsent();
-        } else if (mSimState == TelephonyManager.SIM_STATE_LOADED) {
-            mDataThrottler.reset();
-            if (mConfigReady) {
-                createAllApnList();
-                setDataProfilesAsNeeded();
-                setInitialAttachApn();
-                setupDataOnAllConnectableApns(Phone.REASON_SIM_LOADED, RetryFailures.ALWAYS);
-            } else {
-                log("onSimStateUpdated: config not ready yet.");
-            }
-        }
-    }
-
-    private void onApnUnthrottled(String apn) {
-        if (apn != null) {
-            ApnSetting apnSetting = mAllApnSettings.stream()
-                    .filter(as -> apn.equals(as.getApnName()))
-                    .findFirst()
-                    .orElse(null);
-            if (apnSetting != null) {
-                @ApnType int apnTypes = apnSetting.getApnTypeBitmask();
-                mDataThrottler.setRetryTime(apnTypes, RetryManager.NO_SUGGESTED_RETRY_DELAY,
-                        REQUEST_TYPE_NORMAL);
-            } else {
-                loge("EVENT_APN_UNTHROTTLED: Invalid APN passed: " + apn);
-            }
-        } else {
-            loge("EVENT_APN_UNTHROTTLED: apn is null");
-        }
-    }
-
-    private void onTrafficDescriptorsUpdated() {
-        for (ApnContext apnContext : mPrioritySortedApnContexts) {
-            if (apnContext.getApnTypeBitmask() == ApnSetting.TYPE_ENTERPRISE
-                    && apnContext.getApnSetting().getPermanentFailed()) {
-                setupDataOnConnectableApn(
-                        apnContext, Phone.REASON_TRAFFIC_DESCRIPTORS_UPDATED, RetryFailures.ALWAYS);
-            }
-        }
-    }
-
-    private DataConnection checkForCompatibleDataConnection(ApnContext apnContext,
-            ApnSetting nextApn) {
-        int apnType = apnContext.getApnTypeBitmask();
-        ArrayList<ApnSetting> dunSettings = null;
-
-        if (ApnSetting.TYPE_DUN == apnType) {
-            dunSettings = fetchDunApns();
-        }
-        if (DBG) {
-            log("checkForCompatibleDataConnection: apnContext=" + apnContext);
-        }
-
-        DataConnection potentialDc = null;
-        for (DataConnection curDc : mDataConnections.values()) {
-            if (curDc != null) {
-                ApnSetting apnSetting = curDc.getApnSetting();
-                log("apnSetting: " + apnSetting);
-                if (dunSettings != null && dunSettings.size() > 0) {
-                    for (ApnSetting dunSetting : dunSettings) {
-                        //This ignore network type as a check which is ok because that's checked
-                        //when calculating dun candidates.
-                        if (areCompatible(dunSetting, apnSetting)) {
-                            if (curDc.isActive()) {
-                                if (DBG) {
-                                    log("checkForCompatibleDataConnection:"
-                                            + " found dun conn=" + curDc);
-                                }
-                                return curDc;
-                            } else if (curDc.isActivating()) {
-                                potentialDc = curDc;
-                            }
-                        }
-                    }
-                } else if (isApnSettingCompatible(curDc, apnType)) {
-                    if (curDc.isActive()) {
-                        if (DBG) {
-                            log("checkForCompatibleDataConnection:"
-                                    + " found canHandle conn=" + curDc);
-                        }
-                        return curDc;
-                    } else if (curDc.isActivating()
-                            || (apnSetting !=  null && apnSetting.equals(nextApn))) {
-                        potentialDc = curDc;
-                    }
-                }
-            }
-        }
-
-        if (DBG) {
-            log("checkForCompatibleDataConnection: potential dc=" + potentialDc);
-        }
-        return potentialDc;
-    }
-
-    private boolean isApnSettingCompatible(DataConnection dc, int apnType) {
-        ApnSetting apnSetting = dc.getApnSetting();
-        if (apnSetting == null) return false;
-
-        // Nothing can be compatible with type ENTERPRISE
-        for (ApnContext apnContext : dc.getApnContexts()) {
-            if (apnContext.getApnTypeBitmask() == ApnSetting.TYPE_ENTERPRISE) {
-                return false;
-            }
-        }
-
-        return apnSetting.canHandleType(apnType);
-    }
-
-    private void addHandoverCompleteMsg(Message onCompleteMsg,
-            @ApnType int apnType) {
-        if (onCompleteMsg != null) {
-            List<Message> messageList = mHandoverCompletionMsgs.get(apnType);
-            if (messageList == null) messageList = new ArrayList<>();
-            messageList.add(onCompleteMsg);
-            mHandoverCompletionMsgs.put(apnType, messageList);
-        }
-    }
-
-    private void sendHandoverCompleteMessages(@ApnType int apnType, boolean success,
-            boolean fallbackOnFailedHandover) {
-        List<Message> messageList = mHandoverCompletionMsgs.get(apnType);
-        if (messageList != null) {
-            for (Message msg : messageList) {
-                sendHandoverCompleteMsg(msg, success, mTransportType, fallbackOnFailedHandover);
-            }
-            messageList.clear();
-        }
-    }
-
-    private void sendHandoverCompleteMsg(Message message, boolean success,
-            @TransportType int transport, boolean doFallbackOnFailedHandover) {
-        if (message == null) return;
-
-        Bundle b = message.getData();
-        b.putBoolean(DATA_COMPLETE_MSG_EXTRA_SUCCESS, success);
-        b.putInt(DATA_COMPLETE_MSG_EXTRA_TRANSPORT_TYPE, transport);
-        b.putBoolean(DATA_COMPLETE_MSG_EXTRA_HANDOVER_FAILURE_FALLBACK, doFallbackOnFailedHandover);
-        message.sendToTarget();
-    }
-
-    private static boolean shouldFallbackOnFailedHandover(
-                               @HandoverFailureMode int handoverFailureMode,
-                               @RequestNetworkType int requestType,
-                               @DataFailureCause int cause) {
-        if (requestType != REQUEST_TYPE_HANDOVER) {
-            //The fallback is only relevant if the request is a handover
-            return false;
-        } else if (handoverFailureMode == HANDOVER_FAILURE_MODE_DO_FALLBACK) {
-            return true;
-        } else if (handoverFailureMode == HANDOVER_FAILURE_MODE_LEGACY) {
-            return cause == DataFailCause.HANDOFF_PREFERENCE_CHANGED;
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * Calculates the new request type that will be used the next time a data connection retries
-     * after a failed data call attempt.
-     */
-    @RequestNetworkType
-    public static int calculateNewRetryRequestType(@HandoverFailureMode int handoverFailureMode,
-            @RequestNetworkType int requestType,
-            @DataFailureCause int cause) {
-        boolean fallbackOnFailedHandover =
-                shouldFallbackOnFailedHandover(handoverFailureMode, requestType, cause);
-        if (requestType != REQUEST_TYPE_HANDOVER) {
-            //The fallback is only relevant if the request is a handover
-            return requestType;
-        }
-
-        if (fallbackOnFailedHandover) {
-            // Since fallback is happening, the request type is really "NONE".
-            return REQUEST_TYPE_NORMAL;
-        }
-
-        if (handoverFailureMode == HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_SETUP_NORMAL) {
-            return REQUEST_TYPE_NORMAL;
-        }
-
-        return REQUEST_TYPE_HANDOVER;
-    }
-
-    public void enableApn(@ApnType int apnType, @RequestNetworkType int requestType,
-            Message onHandoverCompleteMsg) {
-        sendMessage(obtainMessage(DctConstants.EVENT_ENABLE_APN, apnType, requestType,
-                onHandoverCompleteMsg));
-    }
-
-    private void onEnableApn(@ApnType int apnType, @RequestNetworkType int requestType,
-            Message onHandoverCompleteMsg) {
-        ApnContext apnContext = mApnContextsByType.get(apnType);
-        if (apnContext == null) {
-            loge("onEnableApn(" + apnType + "): NO ApnContext");
-            if (onHandoverCompleteMsg != null) {
-                sendHandoverCompleteMsg(onHandoverCompleteMsg, false, mTransportType, false);
-            }
-            return;
-        }
-
-        String str = "onEnableApn: apnType=" + ApnSetting.getApnTypeString(apnType)
-                + ", request type=" + requestTypeToString(requestType);
-        if (DBG) log(str);
-        ApnContext.requestLog(apnContext, str);
-
-        if (!apnContext.isDependencyMet()) {
-            apnContext.setReason(Phone.REASON_DATA_DEPENDENCY_UNMET);
-            apnContext.setEnabled(true);
-            str = "onEnableApn: dependency is not met.";
-            if (DBG) log(str);
-            ApnContext.requestLog(apnContext, str);
-            if (onHandoverCompleteMsg != null) {
-                sendHandoverCompleteMsg(onHandoverCompleteMsg, false, mTransportType, false);
-            }
-            return;
-        }
-
-        if (apnContext.isReady()) {
-            DctConstants.State state = apnContext.getState();
-            switch(state) {
-                case CONNECTING:
-                    if (onHandoverCompleteMsg != null) {
-                        if (DBG) {
-                            log("onEnableApn: already in CONNECTING state. Handover request "
-                                    + "will be responded after connected.");
-                        }
-                        addHandoverCompleteMsg(onHandoverCompleteMsg, apnType);
-                    } else {
-                        if (DBG) log("onEnableApn: in CONNECTING state. Exit now.");
-                    }
-                    return;
-                case CONNECTED:
-                    if (onHandoverCompleteMsg != null) {
-                        sendHandoverCompleteMsg(onHandoverCompleteMsg, true, mTransportType,
-                                false);
-                        if (DBG) {
-                            log("onEnableApn: already in CONNECTED state. Consider as handover "
-                                    + "succeeded");
-                        }
-                    } else {
-                        if (DBG) log("onEnableApn: APN in CONNECTED state. Exit now.");
-                    }
-                    return;
-                case IDLE:
-                case FAILED:
-                case RETRYING:
-                    // We're "READY" but not active so disconnect (cleanup = true) and
-                    // connect (trySetup = true) to be sure we retry the connection.
-                    apnContext.setReason(Phone.REASON_DATA_ENABLED);
-                    break;
-            }
-        } else {
-            if (apnContext.isEnabled()) {
-                apnContext.setReason(Phone.REASON_DATA_DEPENDENCY_MET);
-            } else {
-                apnContext.setReason(Phone.REASON_DATA_ENABLED);
-            }
-            if (apnContext.getState() == DctConstants.State.FAILED) {
-                apnContext.setState(DctConstants.State.IDLE);
-            }
-        }
-        apnContext.setEnabled(true);
-        apnContext.resetErrorCodeRetries();
-
-        if (mConfigReady || apnContext.getApnTypeBitmask() == ApnSetting.TYPE_EMERGENCY) {
-            trySetupData(apnContext, requestType, onHandoverCompleteMsg);
-        } else {
-            log("onEnableApn: config not ready yet.");
-        }
-    }
-
-    public void disableApn(@ApnType int apnType, @ReleaseNetworkType int releaseType) {
-        sendMessage(obtainMessage(DctConstants.EVENT_DISABLE_APN, apnType, releaseType));
-    }
-
-    private void onDisableApn(@ApnType int apnType,
-                              @ReleaseNetworkType int releaseType) {
-        ApnContext apnContext = mApnContextsByType.get(apnType);
-        if (apnContext == null) {
-            loge("disableApn(" + apnType + "): NO ApnContext");
-            return;
-        }
-
-        boolean cleanup = false;
-        String str = "onDisableApn: apnType=" + ApnSetting.getApnTypeString(apnType)
-                + ", release type=" + releaseTypeToString(releaseType);
-        if (DBG) log(str);
-        ApnContext.requestLog(apnContext, str);
-
-        if (apnContext.isReady()) {
-            cleanup = (releaseType == RELEASE_TYPE_DETACH
-                    || releaseType == RELEASE_TYPE_HANDOVER);
-            if (apnContext.isDependencyMet()) {
-                apnContext.setReason(Phone.REASON_DATA_DISABLED_INTERNAL);
-                // If ConnectivityService has disabled this network, stop trying to bring
-                // it up, but do not tear it down - ConnectivityService will do that
-                // directly by talking with the DataConnection.
-                //
-                // This doesn't apply to DUN. When the user disable tethering, we would like to
-                // detach the APN context from the data connection so the data connection can be
-                // torn down if no other APN context attached to it.
-                if (ApnSetting.TYPE_DUN_STRING.equals(apnContext.getApnType())
-                        || apnContext.getState() != DctConstants.State.CONNECTED) {
-                    str = "Clean up the connection. Apn type = " + apnContext.getApnType()
-                            + ", state = " + apnContext.getState();
-                    if (DBG) log(str);
-                    ApnContext.requestLog(apnContext, str);
-                    cleanup = true;
-                }
-            } else {
-                apnContext.setReason(Phone.REASON_DATA_DEPENDENCY_UNMET);
-            }
-        }
-
-        apnContext.setEnabled(false);
-        if (cleanup) {
-            cleanUpConnectionInternal(true, releaseType, apnContext);
-        }
-
-        if (isOnlySingleDcAllowed(getDataRat()) && !isHigherPriorityApnContextActive(apnContext)) {
-            if (DBG) log("disableApn:isOnlySingleDcAllowed true & higher priority APN disabled");
-            // If the highest priority APN is disabled and only single
-            // data call is allowed, try to setup data call on other connectable APN.
-            setupDataOnAllConnectableApns(Phone.REASON_SINGLE_PDN_ARBITRATION,
-                    RetryFailures.ALWAYS);
-        }
-    }
-
-    /**
-     * Modify {@link android.provider.Settings.Global#DATA_ROAMING} value for user modification only
-     */
-    public void setDataRoamingEnabledByUser(boolean enabled) {
-        mDataEnabledSettings.setDataRoamingEnabled(enabled);
-        setDataRoamingFromUserAction(true);
-        if (DBG) {
-            log("setDataRoamingEnabledByUser: set phoneSubId=" + mPhone.getSubId()
-                    + " isRoaming=" + enabled);
-        }
-    }
-
-    /**
-     * Return current {@link android.provider.Settings.Global#DATA_ROAMING} value.
-     */
-    public boolean getDataRoamingEnabled() {
-        boolean isDataRoamingEnabled = mDataEnabledSettings.getDataRoamingEnabled();
-
-        if (VDBG) {
-            log("getDataRoamingEnabled: phoneSubId=" + mPhone.getSubId()
-                    + " isDataRoamingEnabled=" + isDataRoamingEnabled);
-        }
-        return isDataRoamingEnabled;
-    }
-
-    /**
-     * Set default value for {@link android.provider.Settings.Global#DATA_ROAMING}
-     * if the setting is not from user actions. default value is based on carrier config and system
-     * properties.
-     */
-    private void setDefaultDataRoamingEnabled() {
-        // For single SIM phones, this is a per phone property.
-        String setting = Settings.Global.DATA_ROAMING;
-        boolean useCarrierSpecificDefault = false;
-        if (mTelephonyManager.getSimCount() != 1) {
-            setting = setting + mPhone.getSubId();
-            try {
-                Settings.Global.getInt(mResolver, setting);
-            } catch (SettingNotFoundException ex) {
-                // For msim, update to carrier default if uninitialized.
-                useCarrierSpecificDefault = true;
-            }
-        } else if (!isDataRoamingFromUserAction()) {
-            // for single sim device, update to carrier default if user action is not set
-            useCarrierSpecificDefault = true;
-        }
-        log("setDefaultDataRoamingEnabled: useCarrierSpecificDefault "
-                + useCarrierSpecificDefault);
-        if (useCarrierSpecificDefault) {
-            boolean defaultVal = mDataEnabledSettings.getDefaultDataRoamingEnabled();
-            mDataEnabledSettings.setDataRoamingEnabled(defaultVal);
-        }
-    }
-
-    private boolean isDataRoamingFromUserAction() {
-        final SharedPreferences sp = PreferenceManager
-                .getDefaultSharedPreferences(mPhone.getContext());
-        // since we don't want to unset user preference from system update, pass true as the default
-        // value if shared pref does not exist and set shared pref to false explicitly from factory
-        // reset.
-        if (!sp.contains(Phone.DATA_ROAMING_IS_USER_SETTING_KEY)) {
-            sp.edit().putBoolean(Phone.DATA_ROAMING_IS_USER_SETTING_KEY, false).commit();
-        }
-        return sp.getBoolean(Phone.DATA_ROAMING_IS_USER_SETTING_KEY, true);
-    }
-
-    private void setDataRoamingFromUserAction(boolean isUserAction) {
-        final SharedPreferences.Editor sp = PreferenceManager
-                .getDefaultSharedPreferences(mPhone.getContext()).edit();
-        sp.putBoolean(Phone.DATA_ROAMING_IS_USER_SETTING_KEY, isUserAction).commit();
-    }
-
-    // When the data roaming status changes from roaming to non-roaming.
-    private void onDataRoamingOff() {
-        if (DBG) log("onDataRoamingOff");
-
-        reevaluateDataConnections();
-
-        if (!getDataRoamingEnabled()) {
-            // TODO: Remove this once all old vendor RILs are gone. We don't need to set initial apn
-            // attach and send the data profile again as the modem should have both roaming and
-            // non-roaming protocol in place. Modem should choose the right protocol based on the
-            // roaming condition.
-            setDataProfilesAsNeeded();
-            setInitialAttachApn();
-
-            // If the user did not enable data roaming, now when we transit from roaming to
-            // non-roaming, we should try to reestablish the data connection.
-
-            setupDataOnAllConnectableApns(Phone.REASON_ROAMING_OFF, RetryFailures.ALWAYS);
-        }
-    }
-
-    // This method is called
-    // 1. When the data roaming status changes from non-roaming to roaming.
-    // 2. When allowed data roaming settings is changed by the user.
-    private void onDataRoamingOnOrSettingsChanged(int messageType) {
-        if (DBG) log("onDataRoamingOnOrSettingsChanged");
-        // Used to differentiate data roaming turned on vs settings changed.
-        boolean settingChanged = (messageType == DctConstants.EVENT_ROAMING_SETTING_CHANGE);
-
-        // Check if the device is actually data roaming
-        if (!mPhone.getServiceState().getDataRoaming()) {
-            if (DBG) log("device is not roaming. ignored the request.");
-            return;
-        }
-
-        checkDataRoamingStatus(settingChanged);
-
-        if (getDataRoamingEnabled()) {
-            // If the restricted data was brought up when data roaming is disabled, and now users
-            // enable data roaming, we need to re-evaluate the conditions and possibly change the
-            // network's capability.
-            if (settingChanged) {
-                reevaluateDataConnections();
-            }
-
-            if (DBG) log("onDataRoamingOnOrSettingsChanged: setup data on roaming");
-
-            setupDataOnAllConnectableApns(Phone.REASON_ROAMING_ON, RetryFailures.ALWAYS);
-        } else {
-            // If the user does not turn on data roaming, when we transit from non-roaming to
-            // roaming, we need to tear down the data connection otherwise the user might be
-            // charged for data roaming usage.
-            if (DBG) log("onDataRoamingOnOrSettingsChanged: Tear down data connection on roaming.");
-            cleanUpAllConnectionsInternal(true, Phone.REASON_ROAMING_ON);
-        }
-    }
-
-    // We want to track possible roaming data leakage. Which is, if roaming setting
-    // is disabled, yet we still setup a roaming data connection or have a connected ApnContext
-    // switched to roaming. When this happens, we log it in a local log.
-    private void checkDataRoamingStatus(boolean settingChanged) {
-        if (!settingChanged && !getDataRoamingEnabled()
-                && mPhone.getServiceState().getDataRoaming()) {
-            for (ApnContext apnContext : mApnContexts.values()) {
-                if (apnContext.getState() == DctConstants.State.CONNECTED) {
-                    mDataRoamingLeakageLog.log("PossibleRoamingLeakage "
-                            + " connection params: " + (apnContext.getDataConnection() != null
-                            ? apnContext.getDataConnection().getConnectionParams() : ""));
-                }
-            }
-        }
-    }
-
-    private void onRadioAvailable() {
-        if (DBG) log("onRadioAvailable");
-        if (!areAllDataDisconnected()) {
-            cleanUpConnectionInternal(true, RELEASE_TYPE_DETACH, null);
-        }
-    }
-
-    private void onRadioOffOrNotAvailable() {
-        // Make sure our reconnect delay starts at the initial value
-        // next time the radio comes on
-
-        mReregisterOnReconnectFailure = false;
-
-        // Clear auto attach as modem is expected to do a new attach
-        mAutoAttachEnabled.set(false);
-
-        if (mPhone.getSimulatedRadioControl() != null) {
-            // Assume data is connected on the simulator
-            // FIXME  this can be improved
-            log("We're on the simulator; assuming radio off is meaningless");
-        } else {
-            if (DBG) log("onRadioOffOrNotAvailable: is off and clean up all connections");
-            cleanUpAllConnectionsInternal(false, Phone.REASON_RADIO_TURNED_OFF);
-        }
-    }
-
-    private void completeConnection(ApnContext apnContext, @RequestNetworkType int type) {
-
-        if (DBG) log("completeConnection: successful, notify the world apnContext=" + apnContext);
-
-        if (mIsProvisioning && !TextUtils.isEmpty(mProvisioningUrl)) {
-            if (DBG) {
-                log("completeConnection: MOBILE_PROVISIONING_ACTION url="
-                        + mProvisioningUrl);
-            }
-            Intent newIntent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN,
-                    Intent.CATEGORY_APP_BROWSER);
-            newIntent.setData(Uri.parse(mProvisioningUrl));
-            newIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
-                    Intent.FLAG_ACTIVITY_NEW_TASK);
-            try {
-                mPhone.getContext().startActivity(newIntent);
-            } catch (ActivityNotFoundException e) {
-                loge("completeConnection: startActivityAsUser failed" + e);
-            }
-        }
-        mIsProvisioning = false;
-        mProvisioningUrl = null;
-        if (mProvisioningSpinner != null) {
-            sendMessage(obtainMessage(DctConstants.CMD_CLEAR_PROVISIONING_SPINNER,
-                    mProvisioningSpinner));
-        }
-
-        startNetStatPoll();
-        startDataStallAlarm(DATA_STALL_NOT_SUSPECTED);
-
-        PersistableBundle b = getCarrierConfig();
-        if (apnContext.getApnTypeBitmask() == ApnSetting.TYPE_DEFAULT
-                && b.getBoolean(CarrierConfigManager
-                .KEY_DISPLAY_NO_DATA_NOTIFICATION_ON_PERMANENT_FAILURE_BOOL)) {
-            NotificationManager notificationManager = (NotificationManager)
-                    mPhone.getContext().getSystemService(Context.NOTIFICATION_SERVICE);
-            notificationManager.cancel(Integer.toString(mPhone.getSubId()),
-                    NO_DATA_NOTIFICATION);
-        }
-    }
-
-    /**
-     * A SETUP (aka bringUp) has completed, possibly with an error. If
-     * there is an error this method will call {@link #onDataSetupCompleteError}.
-     */
-    protected void onDataSetupComplete(ApnContext apnContext, boolean success,
-            @DataFailureCause int cause, @RequestNetworkType int requestType,
-            @HandoverFailureMode int handoverFailureMode) {
-        boolean fallbackOnFailedHandover = shouldFallbackOnFailedHandover(
-                handoverFailureMode, requestType, cause);
-
-        if (success && (handoverFailureMode != DataCallResponse.HANDOVER_FAILURE_MODE_UNKNOWN
-                && handoverFailureMode != DataCallResponse.HANDOVER_FAILURE_MODE_LEGACY)) {
-            Log.wtf(mLogTag, "bad failure mode: "
-                    + DataCallResponse.failureModeToString(handoverFailureMode));
-        } else if (handoverFailureMode
-                != DataCallResponse.HANDOVER_FAILURE_MODE_NO_FALLBACK_RETRY_HANDOVER
-                && cause != DataFailCause.SERVICE_TEMPORARILY_UNAVAILABLE) {
-            sendHandoverCompleteMessages(apnContext.getApnTypeBitmask(), success,
-                    fallbackOnFailedHandover);
-        }
-
-        if (success) {
-            DataConnection dataConnection = apnContext.getDataConnection();
-
-            if (RADIO_TESTS) {
-                // Note: To change radio.test.onDSC.null.dcac from command line you need to
-                // adb root and adb remount and from the command line you can only change the
-                // value to 1 once. To change it a second time you can reboot or execute
-                // adb shell stop and then adb shell start. The command line to set the value is:
-                // adb shell sqlite3 /data/data/com.android.providers.settings/databases/settings.db "insert into system (name,value) values ('radio.test.onDSC.null.dcac', '1');"
-                ContentResolver cr = mPhone.getContext().getContentResolver();
-                String radioTestProperty = "radio.test.onDSC.null.dcac";
-                if (Settings.System.getInt(cr, radioTestProperty, 0) == 1) {
-                    log("onDataSetupComplete: " + radioTestProperty +
-                            " is true, set dcac to null and reset property to false");
-                    dataConnection = null;
-                    Settings.System.putInt(cr, radioTestProperty, 0);
-                    log("onDataSetupComplete: " + radioTestProperty + "=" +
-                            Settings.System.getInt(mPhone.getContext().getContentResolver(),
-                                    radioTestProperty, -1));
-                }
-            }
-            if (dataConnection == null) {
-                log("onDataSetupComplete: no connection to DC, handle as error");
-                onDataSetupCompleteError(apnContext, requestType, false);
-            } else {
-                ApnSetting apn = apnContext.getApnSetting();
-                if (DBG) {
-                    log("onDataSetupComplete: success apn=" + (apn == null ? "unknown"
-                            : apn.getApnName()));
-                }
-
-                // everything is setup
-                if (TextUtils.equals(apnContext.getApnType(), ApnSetting.TYPE_DEFAULT_STRING)
-                        && mCanSetPreferApn && mPreferredApn == null) {
-                    if (DBG) log("onDataSetupComplete: PREFERRED APN is null");
-                    mPreferredApn = apn;
-                    if (mPreferredApn != null) {
-                        setPreferredApn(mPreferredApn.getId());
-                    }
-                }
-
-                // A connection is setup
-                apnContext.setState(DctConstants.State.CONNECTED);
-
-                checkDataRoamingStatus(false);
-
-                boolean isProvApn = apnContext.isProvisioningApn();
-                final ConnectivityManager cm = (ConnectivityManager) mPhone.getContext()
-                        .getSystemService(Context.CONNECTIVITY_SERVICE);
-                if (mProvisionBroadcastReceiver != null) {
-                    mPhone.getContext().unregisterReceiver(mProvisionBroadcastReceiver);
-                    mProvisionBroadcastReceiver = null;
-                }
-
-                if ((!isProvApn) || mIsProvisioning) {
-                    if (mIsProvisioning) {
-                        // Hide any notification that was showing previously
-                        hideProvisioningNotification();
-                    }
-
-                    // Complete the connection normally notifying the world we're connected.
-                    // We do this if this isn't a special provisioning apn or if we've been
-                    // told its time to provision.
-                    completeConnection(apnContext, requestType);
-                } else {
-                    // This is a provisioning APN that we're reporting as connected. Later
-                    // when the user desires to upgrade this to a "default" connection,
-                    // mIsProvisioning == true, we'll go through the code path above.
-                    // mIsProvisioning becomes true when CMD_ENABLE_MOBILE_PROVISIONING
-                    // is sent to the DCT.
-                    if (DBG) {
-                        log("onDataSetupComplete: successful, BUT send connected to prov apn as"
-                                + " mIsProvisioning:" + mIsProvisioning + " == false"
-                                + " && (isProvisioningApn:" + isProvApn + " == true");
-                    }
-
-                    // While radio is up, grab provisioning URL.  The URL contains ICCID which
-                    // disappears when radio is off.
-                    mProvisionBroadcastReceiver = new ProvisionNotificationBroadcastReceiver(
-                            mPhone.getMobileProvisioningUrl(),
-                            mTelephonyManager.getNetworkOperatorName());
-                    mPhone.getContext().registerReceiver(mProvisionBroadcastReceiver,
-                            new IntentFilter(INTENT_PROVISION));
-
-                    // Put up user notification that sign-in is required.
-                    showProvisioningNotification();
-
-                    // Turn off radio to save battery and avoid wasting carrier resources.
-                    // The network isn't usable and network validation will just fail anyhow.
-                    setRadio(false);
-                }
-                if (DBG) {
-                    log("onDataSetupComplete: SETUP complete type=" + apnContext.getApnType());
-                }
-                if (TelephonyUtils.IS_DEBUGGABLE) {
-                    // adb shell setprop persist.radio.test.pco [pco_val]
-                    String radioTestProperty = "persist.radio.test.pco";
-                    int pcoVal = SystemProperties.getInt(radioTestProperty, -1);
-                    if (pcoVal != -1) {
-                        log("PCO testing: read pco value from persist.radio.test.pco " + pcoVal);
-                        final byte[] value = new byte[1];
-                        value[0] = (byte) pcoVal;
-                        final Intent intent =
-                                new Intent(TelephonyManager.ACTION_CARRIER_SIGNAL_PCO_VALUE);
-                        intent.putExtra(TelephonyManager.EXTRA_APN_TYPE, ApnSetting.TYPE_DEFAULT);
-                        intent.putExtra(TelephonyManager.EXTRA_APN_PROTOCOL,
-                                ApnSetting.PROTOCOL_IPV4V6);
-                        intent.putExtra(TelephonyManager.EXTRA_PCO_ID, 0xFF00);
-                        intent.putExtra(TelephonyManager.EXTRA_PCO_VALUE, value);
-                        mPhone.getCarrierSignalAgent().notifyCarrierSignalReceivers(intent);
-                    }
-                }
-            }
-        } else {
-            if (DBG) {
-                ApnSetting apn = apnContext.getApnSetting();
-                log("onDataSetupComplete: error apn=" + apn.getApnName() + ", cause="
-                        + DataFailCause.toString(cause) + ", requestType="
-                        + requestTypeToString(requestType));
-            }
-            if (DataFailCause.isEventLoggable(cause)) {
-                // Log this failure to the Event Logs.
-                int cid = getCellLocationId();
-                EventLog.writeEvent(EventLogTags.PDP_SETUP_FAIL,
-                        cause, cid, mTelephonyManager.getNetworkType());
-            }
-            ApnSetting apn = apnContext.getApnSetting();
-
-            // Compose broadcast intent send to the specific carrier signaling receivers
-            Intent intent = new Intent(TelephonyManager
-                    .ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED);
-            intent.putExtra(TelephonyManager.EXTRA_DATA_FAIL_CAUSE, cause);
-            intent.putExtra(TelephonyManager.EXTRA_APN_TYPE,
-                    ApnSetting.getApnTypesBitmaskFromString(apnContext.getApnType()));
-            mPhone.getCarrierSignalAgent().notifyCarrierSignalReceivers(intent);
-
-            if (DataFailCause.isRadioRestartFailure(mPhone.getContext(), cause, mPhone.getSubId())
-                    || apnContext.restartOnError(cause)) {
-                if (DBG) log("Modem restarted.");
-                sendRestartRadio();
-            }
-
-            // If the data call failure cause is a permanent failure, we mark the APN as permanent
-            // failed.
-            if (isPermanentFailure(cause)) {
-                log("cause=" + DataFailCause.toString(cause)
-                        + ", mark apn as permanent failed. apn = " + apn);
-                apnContext.markApnPermanentFailed(apn);
-
-                PersistableBundle b = getCarrierConfig();
-                if (apnContext.getApnTypeBitmask() == ApnSetting.TYPE_DEFAULT
-                        && b.getBoolean(CarrierConfigManager
-                        .KEY_DISPLAY_NO_DATA_NOTIFICATION_ON_PERMANENT_FAILURE_BOOL)) {
-                    NotificationManager notificationManager = (NotificationManager)
-                            mPhone.getContext().getSystemService(Context.NOTIFICATION_SERVICE);
-
-                    CharSequence title = mPhone.getContext().getText(
-                            com.android.internal.R.string.RestrictedOnDataTitle);
-                    CharSequence details = mPhone.getContext().getText(
-                            com.android.internal.R.string.RestrictedStateContent);
-
-                    Notification notification = new Notification.Builder(mPhone.getContext(),
-                            NotificationChannelController.CHANNEL_ID_MOBILE_DATA_STATUS)
-                            .setWhen(System.currentTimeMillis())
-                            .setAutoCancel(true)
-                            .setSmallIcon(com.android.internal.R.drawable.stat_sys_warning)
-                            .setTicker(title)
-                            .setColor(mPhone.getContext().getResources().getColor(
-                                    com.android.internal.R.color.system_notification_accent_color))
-                            .setContentTitle(title)
-                            .setStyle(new Notification.BigTextStyle().bigText(details))
-                            .setContentText(details)
-                            .build();
-                    notificationManager.notify(Integer.toString(mPhone.getSubId()),
-                            NO_DATA_NOTIFICATION, notification);
-                }
-            }
-
-            int newRequestType = calculateNewRetryRequestType(handoverFailureMode, requestType,
-                    cause);
-            onDataSetupCompleteError(apnContext, newRequestType, fallbackOnFailedHandover);
-        }
-    }
-
-
-
-    /**
-     * Error has occurred during the SETUP {aka bringUP} request and the DCT
-     * should either try the next waiting APN or start over from the
-     * beginning if the list is empty. Between each SETUP request there will
-     * be a delay defined by {@link ApnContext#getDelayForNextApn(boolean)}.
-     */
-    protected void onDataSetupCompleteError(ApnContext apnContext,
-            @RequestNetworkType int requestType, boolean fallbackOnFailedHandover) {
-        long delay = apnContext.getDelayForNextApn(mFailFast);
-        // Check if we need to retry or not.
-        if (delay >= 0 && delay != RetryManager.NO_RETRY && !fallbackOnFailedHandover) {
-            if (DBG) {
-                log("onDataSetupCompleteError: APN type=" + apnContext.getApnType()
-                        + ". Request type=" + requestTypeToString(requestType) + ", Retry in "
-                        + delay + "ms.");
-            }
-            startReconnect(delay, apnContext, requestType);
-        } else {
-            // If we are not going to retry any APN, set this APN context to failed state.
-            // This would be the final state of a data connection.
-            apnContext.setState(DctConstants.State.FAILED);
-            apnContext.setDataConnection(null);
-            log("onDataSetupCompleteError: Stop retrying APNs. delay=" + delay
-                    + ", requestType=" + requestTypeToString(requestType));
-            //send request network complete messages as needed
-            sendHandoverCompleteMessages(apnContext.getApnTypeBitmask(), false,
-                    fallbackOnFailedHandover);
-        }
-    }
-
-    /**
-     * Called when EVENT_NETWORK_STATUS_CHANGED is received.
-     *
-     * @param status One of {@code NetworkAgent.VALID_NETWORK} or
-     * {@code NetworkAgent.INVALID_NETWORK}.
-     * @param cid context id {@code cid}
-     * @param redirectUrl If the Internet probe was redirected, this
-     * is the destination it was redirected to, otherwise {@code null}
-     */
-    private void onNetworkStatusChanged(int status, int cid, String redirectUrl) {
-        if (!TextUtils.isEmpty(redirectUrl)) {
-            Intent intent = new Intent(TelephonyManager.ACTION_CARRIER_SIGNAL_REDIRECTED);
-            intent.putExtra(TelephonyManager.EXTRA_REDIRECTION_URL, redirectUrl);
-            mPhone.getCarrierSignalAgent().notifyCarrierSignalReceivers(intent);
-            log("Notify carrier signal receivers with redirectUrl: " + redirectUrl);
-        } else {
-            final boolean isValid = status == NetworkAgent.VALIDATION_STATUS_VALID;
-            final DataConnection dc = getDataConnectionByContextId(cid);
-            if (!mDsRecoveryHandler.isRecoveryOnBadNetworkEnabled()) {
-                if (DBG) log("Skip data stall recovery on network status change with in threshold");
-                return;
-            }
-            if (mTransportType != AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
-                if (DBG) log("Skip data stall recovery on non WWAN");
-                return;
-            }
-            if (dc != null && dc.isValidationRequired()) {
-                mDsRecoveryHandler.processNetworkStatusChanged(isValid);
-            }
-        }
-    }
-
-    /**
-     * Called when EVENT_DISCONNECT_DONE is received.
-     */
-    private void onDisconnectDone(ApnContext apnContext) {
-        if(DBG) log("onDisconnectDone: EVENT_DISCONNECT_DONE apnContext=" + apnContext);
-        apnContext.setState(DctConstants.State.IDLE);
-        // If all data connection are gone, check whether Airplane mode request was pending.
-        if (areAllDataDisconnected()
-                && mPhone.getServiceStateTracker().processPendingRadioPowerOffAfterDataOff()) {
-            if (DBG) log("onDisconnectDone: radio will be turned off, no retries");
-            // Radio will be turned off. No need to retry data setup
-            apnContext.setApnSetting(null);
-            apnContext.setDataConnection(null);
-
-            // Need to notify disconnect as well, in the case of switching Airplane mode.
-            // Otherwise, it would cause 30s delayed to turn on Airplane mode.
-            notifyAllDataDisconnected();
-            return;
-        }
-        // If APN is still enabled, try to bring it back up automatically
-        if (mAttached.get() && apnContext.isReady() && retryAfterDisconnected(apnContext)) {
-            // Wait a bit before trying the next APN, so that
-            // we're not tying up the RIL command channel.
-            // This also helps in any external dependency to turn off the context.
-            if (DBG) log("onDisconnectDone: attached, ready and retry after disconnect");
-
-            // See if there are still handover request pending that we need to retry handover
-            // after previous data gets disconnected.
-            if (isHandoverPending(apnContext.getApnTypeBitmask())) {
-                if (DBG) log("Handover request pending. Retry handover immediately.");
-                startReconnect(0, apnContext, REQUEST_TYPE_HANDOVER);
-            } else {
-                long delay = apnContext.getRetryAfterDisconnectDelay();
-                if (delay > 0) {
-                    // Data connection is in IDLE state, so when we reconnect later, we'll rebuild
-                    // the waiting APN list, which will also reset/reconfigure the retry manager.
-                    startReconnect(delay, apnContext, REQUEST_TYPE_NORMAL);
-                }
-            }
-        } else {
-            boolean restartRadioAfterProvisioning = mPhone.getContext().getResources().getBoolean(
-                    com.android.internal.R.bool.config_restartRadioAfterProvisioning);
-
-            if (apnContext.isProvisioningApn() && restartRadioAfterProvisioning) {
-                log("onDisconnectDone: restartRadio after provisioning");
-                restartRadio();
-            }
-            apnContext.setApnSetting(null);
-            apnContext.setDataConnection(null);
-            if (isOnlySingleDcAllowed(getDataRat())) {
-                if(DBG) log("onDisconnectDone: isOnlySigneDcAllowed true so setup single apn");
-                setupDataOnAllConnectableApns(Phone.REASON_SINGLE_PDN_ARBITRATION,
-                        RetryFailures.ALWAYS);
-            } else {
-                if(DBG) log("onDisconnectDone: not retrying");
-            }
-        }
-
-        if (areAllDataDisconnected()) {
-            apnContext.setConcurrentVoiceAndDataAllowed(
-                    mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed());
-            notifyAllDataDisconnected();
-        }
-
-    }
-
-    private void onVoiceCallStarted() {
-        if (DBG) log("onVoiceCallStarted");
-        mInVoiceCall = true;
-        if (isAnyDataConnected()
-                && !mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) {
-            if (DBG) log("onVoiceCallStarted stop polling");
-            stopNetStatPoll();
-            stopDataStallAlarm();
-        }
-    }
-
-    protected void onVoiceCallEnded() {
-        if (DBG) log("onVoiceCallEnded");
-        mInVoiceCall = false;
-        if (isAnyDataConnected()) {
-            if (!mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) {
-                startNetStatPoll();
-                startDataStallAlarm(DATA_STALL_NOT_SUSPECTED);
-            } else {
-                // clean slate after call end.
-                resetPollStats();
-            }
-        }
-        // reset reconnect timer
-        setupDataOnAllConnectableApns(Phone.REASON_VOICE_CALL_ENDED, RetryFailures.ALWAYS);
-    }
-    /**
-     * @return {@code true} if there is any data in connected state.
-     */
-    @VisibleForTesting
-    public boolean isAnyDataConnected() {
-        for (DataConnection dc : mDataConnections.values()) {
-            if (dc.isActive()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * @return {@code true} if all data connections are in disconnected state.
-     */
-    public boolean areAllDataDisconnected() {
-        for (DataConnection dc : mDataConnections.values()) {
-            if (!dc.isInactive()) {
-                if (DBG) log("areAllDataDisconnected false due to DC: " + dc.getName());
-                return false;
-            }
-        }
-        return true;
-    }
-
-    protected void setDataProfilesAsNeeded() {
-        if (DBG) log("setDataProfilesAsNeeded");
-
-        ArrayList<DataProfile> dataProfileList = new ArrayList<>();
-
-        int preferredApnSetId = getPreferredApnSetId();
-        for (ApnSetting apn : mAllApnSettings) {
-            if (apn.getApnSetId() == Telephony.Carriers.MATCH_ALL_APN_SET_ID
-                    || preferredApnSetId == apn.getApnSetId()) {
-                DataProfile dp = new DataProfile.Builder()
-                        .setApnSetting(apn)
-                        .setPreferred(apn.equals(getPreferredApn()))
-                        .build();
-                if (!dataProfileList.contains(dp)) {
-                    dataProfileList.add(dp);
-                }
-            } else {
-                if (VDBG) {
-                    log("setDataProfilesAsNeeded: APN set id " + apn.getApnSetId()
-                            + " does not match the preferred set id " + preferredApnSetId);
-                }
-            }
-        }
-
-        // Check if the data profiles we are sending are same as we did last time. We don't want to
-        // send the redundant profiles to the modem. Also if there the list is empty, we don't
-        // send it to the modem.
-        if (!dataProfileList.isEmpty()
-                && (dataProfileList.size() != mLastDataProfileList.size()
-                || !mLastDataProfileList.containsAll(dataProfileList))) {
-            mDataServiceManager.setDataProfile(dataProfileList,
-                    mPhone.getServiceState().getDataRoamingFromRegistration(), null);
-        }
-    }
-
-    /**
-     * Based on the sim operator numeric, create a list for all possible
-     * Data Connections and setup the preferredApn.
-     */
-    protected void createAllApnList() {
-        mAllApnSettings.clear();
-        String operator = mPhone.getOperatorNumeric();
-
-        // ORDER BY Telephony.Carriers._ID ("_id")
-        Cursor cursor = mPhone.getContext().getContentResolver().query(
-                Uri.withAppendedPath(Telephony.Carriers.SIM_APN_URI, "filtered/subId/"
-                        + mPhone.getSubId()), null, null, null, Telephony.Carriers._ID);
-
-        if (cursor != null) {
-            while (cursor.moveToNext()) {
-                ApnSetting apn = ApnSetting.makeApnSetting(cursor);
-                if (apn == null) {
-                    continue;
-                }
-                mAllApnSettings.add(apn);
-            }
-            cursor.close();
-        } else {
-            if (DBG) log("createAllApnList: cursor is null");
-            mApnSettingsInitializationLog.log("cursor is null for carrier, operator: "
-                    + operator);
-        }
-
-        dedupeApnSettings();
-
-        if (mAllApnSettings.isEmpty()) {
-            log("createAllApnList: No APN found for carrier, operator: " + operator);
-            mApnSettingsInitializationLog.log("no APN found for carrier, operator: "
-                    + operator);
-            mPreferredApn = null;
-        } else {
-            mPreferredApn = getPreferredApn();
-            if (DBG) log("createAllApnList: mPreferredApn=" + mPreferredApn);
-        }
-
-        addDefaultApnSettingsAsNeeded();
-        if (DBG) log("createAllApnList: X mAllApnSettings=" + mAllApnSettings);
-    }
-
-    private void dedupeApnSettings() {
-        ArrayList<ApnSetting> resultApns = new ArrayList<ApnSetting>();
-
-        // coalesce APNs if they are similar enough to prevent
-        // us from bringing up two data calls with the same interface
-        int i = 0;
-        while (i < mAllApnSettings.size() - 1) {
-            ApnSetting first = mAllApnSettings.get(i);
-            ApnSetting second = null;
-            int j = i + 1;
-            while (j < mAllApnSettings.size()) {
-                second = mAllApnSettings.get(j);
-                if (first.similar(second)) {
-                    ApnSetting newApn = mergeApns(first, second);
-                    mAllApnSettings.set(i, newApn);
-                    first = newApn;
-                    mAllApnSettings.remove(j);
-                } else {
-                    j++;
-                }
-            }
-            i++;
-        }
-    }
-
-    private ApnSetting mergeApns(ApnSetting dest, ApnSetting src) {
-        int id = dest.getId();
-        if ((src.getApnTypeBitmask() & ApnSetting.TYPE_DEFAULT) == ApnSetting.TYPE_DEFAULT) {
-            id = src.getId();
-        }
-        final int resultApnType = src.getApnTypeBitmask() | dest.getApnTypeBitmask();
-        Uri mmsc = (dest.getMmsc() == null ? src.getMmsc() : dest.getMmsc());
-        String mmsProxy = TextUtils.isEmpty(dest.getMmsProxyAddressAsString())
-                ? src.getMmsProxyAddressAsString() : dest.getMmsProxyAddressAsString();
-        int mmsPort = dest.getMmsProxyPort() == -1 ? src.getMmsProxyPort() : dest.getMmsProxyPort();
-        String proxy = TextUtils.isEmpty(dest.getProxyAddressAsString())
-                ? src.getProxyAddressAsString() : dest.getProxyAddressAsString();
-        int port = dest.getProxyPort() == -1 ? src.getProxyPort() : dest.getProxyPort();
-        int protocol = src.getProtocol() == ApnSetting.PROTOCOL_IPV4V6 ? src.getProtocol()
-                : dest.getProtocol();
-        int roamingProtocol = src.getRoamingProtocol() == ApnSetting.PROTOCOL_IPV4V6
-                ? src.getRoamingProtocol() : dest.getRoamingProtocol();
-        int networkTypeBitmask = (dest.getNetworkTypeBitmask() == 0
-                || src.getNetworkTypeBitmask() == 0)
-                ? 0 : (dest.getNetworkTypeBitmask() | src.getNetworkTypeBitmask());
-        return new ApnSetting.Builder()
-                .setId(id)
-                .setOperatorNumeric(dest.getOperatorNumeric())
-                .setEntryName(dest.getEntryName())
-                .setApnName(dest.getApnName())
-                .setProxyAddress(proxy)
-                .setProxyPort(port)
-                .setMmsc(mmsc)
-                .setMmsProxyAddress(mmsProxy)
-                .setMmsProxyPort(mmsPort)
-                .setUser(dest.getUser())
-                .setPassword(dest.getPassword())
-                .setAuthType(dest.getAuthType())
-                .setApnTypeBitmask(resultApnType)
-                .setProtocol(protocol)
-                .setRoamingProtocol(roamingProtocol)
-                .setCarrierEnabled(dest.isEnabled())
-                .setNetworkTypeBitmask(networkTypeBitmask)
-                .setProfileId(dest.getProfileId())
-                .setModemCognitive(dest.isPersistent() || src.isPersistent())
-                .setMaxConns(dest.getMaxConns())
-                .setWaitTime(dest.getWaitTime())
-                .setMaxConnsTime(dest.getMaxConnsTime())
-                .setMtuV4(dest.getMtuV4())
-                .setMtuV6(dest.getMtuV6())
-                .setMvnoType(dest.getMvnoType())
-                .setMvnoMatchData(dest.getMvnoMatchData())
-                .setApnSetId(dest.getApnSetId())
-                .setCarrierId(dest.getCarrierId())
-                .setSkip464Xlat(dest.getSkip464Xlat())
-                .build();
-    }
-
-    private DataConnection createDataConnection() {
-        if (DBG) log("createDataConnection E");
-
-        int id = mUniqueIdGenerator.getAndIncrement();
-        DataConnection dataConnection = DataConnection.makeDataConnection(mPhone, id, this,
-                mDataServiceManager, mDcTesterFailBringUpAll, mDcc);
-        mDataConnections.put(id, dataConnection);
-        if (DBG) log("createDataConnection() X id=" + id + " dc=" + dataConnection);
-        return dataConnection;
-    }
-
-    private void destroyDataConnections() {
-        if(mDataConnections != null) {
-            if (DBG) log("destroyDataConnections: clear mDataConnectionList");
-            mDataConnections.clear();
-        } else {
-            if (DBG) log("destroyDataConnections: mDataConnecitonList is empty, ignore");
-        }
-    }
-
-    /**
-     * Build a list of APNs to be used to create PDP's.
-     *
-     * @param requestedApnType
-     * @return waitingApns list to be used to create PDP
-     *          error when waitingApns.isEmpty()
-     */
-    private @NonNull ArrayList<ApnSetting> buildWaitingApns(String requestedApnType,
-            int radioTech) {
-        if (DBG) log("buildWaitingApns: E requestedApnType=" + requestedApnType);
-        ArrayList<ApnSetting> apnList = new ArrayList<ApnSetting>();
-
-        int requestedApnTypeBitmask = ApnSetting.getApnTypesBitmaskFromString(requestedApnType);
-        if (requestedApnTypeBitmask == ApnSetting.TYPE_ENTERPRISE) {
-            requestedApnTypeBitmask = ApnSetting.TYPE_DEFAULT;
-        }
-        if (requestedApnTypeBitmask == ApnSetting.TYPE_DUN) {
-            ArrayList<ApnSetting> dunApns = fetchDunApns();
-            if (dunApns.size() > 0) {
-                for (ApnSetting dun : dunApns) {
-                    apnList.add(dun);
-                    if (DBG) log("buildWaitingApns: X added APN_TYPE_DUN apnList=" + apnList);
-                }
-                return apnList;
-            }
-        }
-
-        String operator = mPhone.getOperatorNumeric();
-
-        // This is a workaround for a bug (7305641) where we don't failover to other
-        // suitable APNs if our preferred APN fails.  On prepaid ATT sims we need to
-        // failover to a provisioning APN, but once we've used their default data
-        // connection we are locked to it for life.  This change allows ATT devices
-        // to say they don't want to use preferred at all.
-        boolean usePreferred = true;
-        try {
-            usePreferred = !mPhone.getContext().getResources().getBoolean(com.android
-                    .internal.R.bool.config_dontPreferApn);
-        } catch (Resources.NotFoundException e) {
-            if (DBG) log("buildWaitingApns: usePreferred NotFoundException set to true");
-            usePreferred = true;
-        }
-        if (usePreferred) {
-            mPreferredApn = getPreferredApn();
-        }
-        if (DBG) {
-            log("buildWaitingApns: usePreferred=" + usePreferred
-                    + " canSetPreferApn=" + mCanSetPreferApn
-                    + " mPreferredApn=" + mPreferredApn
-                    + " operator=" + operator + " radioTech=" + radioTech);
-        }
-
-        if (usePreferred && mCanSetPreferApn && mPreferredApn != null &&
-                mPreferredApn.canHandleType(requestedApnTypeBitmask)) {
-            if (DBG) {
-                log("buildWaitingApns: Preferred APN:" + operator + ":"
-                        + mPreferredApn.getOperatorNumeric() + ":" + mPreferredApn);
-            }
-
-            if (TextUtils.equals(mPreferredApn.getOperatorNumeric(), operator)
-                    || mPreferredApn.getCarrierId() == mPhone.getCarrierId()) {
-                if (mPreferredApn.canSupportNetworkType(
-                        ServiceState.rilRadioTechnologyToNetworkType(radioTech))) {
-                    // Create a new instance of ApnSetting for ENTERPRISE because each
-                    // DataConnection should have its own ApnSetting. ENTERPRISE uses the same
-                    // APN as DEFAULT but is a separate DataConnection
-                    if (ApnSetting.getApnTypesBitmaskFromString(requestedApnType)
-                            == ApnSetting.TYPE_ENTERPRISE) {
-                        apnList.add(ApnSetting.makeApnSetting(mPreferredApn));
-                    } else {
-                        apnList.add(mPreferredApn);
-                    }
-                    if (DBG) log("buildWaitingApns: X added preferred apnList=" + apnList);
-                    return apnList;
-                }
-            }
-            if (DBG) log("buildWaitingApns: no preferred APN");
-            setPreferredApn(-1);
-            mPreferredApn = null;
-        }
-
-        if (DBG) log("buildWaitingApns: mAllApnSettings=" + mAllApnSettings);
-        int preferredApnSetId = getPreferredApnSetId();
-        for (ApnSetting apn : mAllApnSettings) {
-            if (apn.canHandleType(requestedApnTypeBitmask)) {
-                if (apn.canSupportNetworkType(
-                        ServiceState.rilRadioTechnologyToNetworkType(radioTech))) {
-                    if (apn.getApnSetId() == Telephony.Carriers.MATCH_ALL_APN_SET_ID
-                            || preferredApnSetId == apn.getApnSetId()) {
-                        if (VDBG) log("buildWaitingApns: adding apn=" + apn);
-                        // Create a new instance of ApnSetting for ENTERPRISE because each
-                        // DataConnection should have its own ApnSetting. ENTERPRISE uses the same
-                        // APN as DEFAULT but is a separate DataConnection
-                        if (ApnSetting.getApnTypesBitmaskFromString(requestedApnType)
-                                == ApnSetting.TYPE_ENTERPRISE) {
-                            apnList.add(ApnSetting.makeApnSetting(apn));
-                        } else {
-                            apnList.add(apn);
-                        }
-                    } else {
-                        log("buildWaitingApns: APN set id " + apn.getApnSetId()
-                                + " does not match the preferred set id " + preferredApnSetId);
-                    }
-                } else {
-                    if (DBG) {
-                        log("buildWaitingApns: networkTypeBitmask:"
-                                + apn.getNetworkTypeBitmask()
-                                + " does not include radioTech:"
-                                + ServiceState.rilRadioTechnologyToString(radioTech));
-                    }
-                }
-            } else if (VDBG) {
-                log("buildWaitingApns: couldn't handle requested ApnType="
-                        + requestedApnType);
-            }
-        }
-
-        if (DBG) log("buildWaitingApns: " + apnList.size() + " APNs in the list: " + apnList);
-        return apnList;
-    }
-
-    private String apnListToString (ArrayList<ApnSetting> apns) {
-        StringBuilder result = new StringBuilder();
-        for (int i = 0, size = apns.size(); i < size; i++) {
-            result.append('[')
-                  .append(apns.get(i).toString())
-                  .append(']');
-        }
-        return result.toString();
-    }
-
-    private void setPreferredApn(int pos) {
-        setPreferredApn(pos, false);
-    }
-
-    private void setPreferredApn(int pos, boolean force) {
-        if (!force && !mCanSetPreferApn) {
-            log("setPreferredApn: X !canSEtPreferApn");
-            return;
-        }
-
-        String subId = Long.toString(mPhone.getSubId());
-        Uri uri = Uri.withAppendedPath(PREFERAPN_NO_UPDATE_URI_USING_SUBID, subId);
-        log("setPreferredApn: delete");
-        ContentResolver resolver = mPhone.getContext().getContentResolver();
-        resolver.delete(uri, null, null);
-
-        if (pos >= 0) {
-            log("setPreferredApn: insert");
-            ContentValues values = new ContentValues();
-            values.put(APN_ID, pos);
-            resolver.insert(uri, values);
-        }
-    }
-
-    @Nullable
-    ApnSetting getPreferredApn() {
-        //Only call this method from main thread
-        if (mAllApnSettings == null || mAllApnSettings.isEmpty()) {
-            log("getPreferredApn: mAllApnSettings is empty");
-            return null;
-        }
-
-        String subId = Long.toString(mPhone.getSubId());
-        Uri uri = Uri.withAppendedPath(PREFERAPN_NO_UPDATE_URI_USING_SUBID, subId);
-        Cursor cursor = mPhone.getContext().getContentResolver().query(
-                uri, new String[] { "_id", "name", "apn" },
-                null, null, Telephony.Carriers.DEFAULT_SORT_ORDER);
-
-        if (cursor != null) {
-            mCanSetPreferApn = true;
-        } else {
-            mCanSetPreferApn = false;
-        }
-
-        if (VDBG) {
-            log("getPreferredApn: mRequestedApnType=" + mRequestedApnType + " cursor=" + cursor
-                    + " cursor.count=" + ((cursor != null) ? cursor.getCount() : 0));
-        }
-
-        if (mCanSetPreferApn && cursor.getCount() > 0) {
-            int pos;
-            cursor.moveToFirst();
-            pos = cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers._ID));
-            for(ApnSetting p : mAllApnSettings) {
-                if (p.getId() == pos && p.canHandleType(mRequestedApnType)) {
-                    log("getPreferredApn: For APN type "
-                            + ApnSetting.getApnTypeString(mRequestedApnType)
-                            + " found apnSetting " + p);
-                    cursor.close();
-                    return p;
-                }
-            }
-        }
-
-        if (cursor != null) {
-            cursor.close();
-        }
-
-        log("getPreferredApn: X not found");
-        return null;
-    }
-
-    @Override
-    public void handleMessage (Message msg) {
-        if (VDBG) log("handleMessage msg=" + msg);
-
-        AsyncResult ar;
-        Pair<ApnContext, Integer> pair;
-        ApnContext apnContext;
-        int generation;
-        int requestType;
-        int handoverFailureMode;
-        switch (msg.what) {
-            case DctConstants.EVENT_DATA_CONNECTION_DETACHED:
-                onDataConnectionDetached();
-                break;
-
-            case DctConstants.EVENT_DATA_CONNECTION_ATTACHED:
-                onDataConnectionAttached();
-                break;
-
-            case DctConstants.EVENT_DO_RECOVERY:
-                mDsRecoveryHandler.doRecovery();
-                break;
-
-            case DctConstants.EVENT_APN_CHANGED:
-                onApnChanged();
-                break;
-
-            case DctConstants.EVENT_PS_RESTRICT_ENABLED:
-                /**
-                 * We don't need to explicitly to tear down the PDP context
-                 * when PS restricted is enabled. The base band will deactive
-                 * PDP context and notify us with PDP_CONTEXT_CHANGED.
-                 * But we should stop the network polling and prevent reset PDP.
-                 */
-                if (DBG) log("EVENT_PS_RESTRICT_ENABLED " + mIsPsRestricted);
-                stopNetStatPoll();
-                stopDataStallAlarm();
-                mIsPsRestricted = true;
-                break;
-
-            case DctConstants.EVENT_PS_RESTRICT_DISABLED:
-                /**
-                 * When PS restrict is removed, we need setup PDP connection if
-                 * PDP connection is down.
-                 */
-                if (DBG) log("EVENT_PS_RESTRICT_DISABLED " + mIsPsRestricted);
-                mIsPsRestricted  = false;
-                if (isAnyDataConnected()) {
-                    startNetStatPoll();
-                    startDataStallAlarm(DATA_STALL_NOT_SUSPECTED);
-                } else {
-                    // TODO: Should all PDN states be checked to fail?
-                    if (mState == DctConstants.State.FAILED) {
-                        cleanUpAllConnectionsInternal(false, Phone.REASON_PS_RESTRICT_ENABLED);
-                        mReregisterOnReconnectFailure = false;
-                    }
-                    apnContext = mApnContextsByType.get(ApnSetting.TYPE_DEFAULT);
-                    if (apnContext != null) {
-                        apnContext.setReason(Phone.REASON_PS_RESTRICT_ENABLED);
-                        trySetupData(apnContext, REQUEST_TYPE_NORMAL, null);
-                    } else {
-                        loge("**** Default ApnContext not found ****");
-                        if (TelephonyUtils.IS_DEBUGGABLE) {
-                            throw new RuntimeException("Default ApnContext not found");
-                        }
-                    }
-                }
-                break;
-
-            case DctConstants.EVENT_TRY_SETUP_DATA:
-                apnContext = (ApnContext) msg.obj;
-                requestType = msg.arg1;
-                trySetupData(apnContext, requestType, null);
-                break;
-            case DctConstants.EVENT_CLEAN_UP_CONNECTION:
-                if (DBG) log("EVENT_CLEAN_UP_CONNECTION");
-                cleanUpConnectionInternal(true, RELEASE_TYPE_DETACH, (ApnContext) msg.obj);
-                break;
-            case DctConstants.EVENT_CLEAN_UP_ALL_CONNECTIONS:
-                if ((msg.obj != null) && (msg.obj instanceof String == false)) {
-                    msg.obj = null;
-                }
-                cleanUpAllConnectionsInternal(true, (String) msg.obj);
-                break;
-
-            case DctConstants.EVENT_DATA_RAT_CHANGED:
-                if (getDataRat() == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN) {
-                    // unknown rat is an exception for data rat change. It's only received when out
-                    // of service and is not applicable for apn bearer bitmask. We should bypass the
-                    // check of waiting apn list and keep the data connection on, and no need to
-                    // setup a new one.
-                    break;
-                }
-                cleanUpConnectionsOnUpdatedApns(false, Phone.REASON_NW_TYPE_CHANGED);
-                //May new Network allow setupData, so try it here
-                setupDataOnAllConnectableApns(Phone.REASON_NW_TYPE_CHANGED,
-                        RetryFailures.ONLY_ON_CHANGE);
-                break;
-
-            case DctConstants.CMD_CLEAR_PROVISIONING_SPINNER:
-                // Check message sender intended to clear the current spinner.
-                if (mProvisioningSpinner == msg.obj) {
-                    mProvisioningSpinner.dismiss();
-                    mProvisioningSpinner = null;
-                }
-                break;
-
-            case DctConstants.EVENT_ENABLE_APN:
-                onEnableApn(msg.arg1, msg.arg2, (Message) msg.obj);
-                break;
-
-            case DctConstants.EVENT_DISABLE_APN:
-                onDisableApn(msg.arg1, msg.arg2);
-                break;
-
-            case DctConstants.EVENT_DATA_STALL_ALARM:
-                onDataStallAlarm(msg.arg1);
-                break;
-
-            case DctConstants.EVENT_ROAMING_OFF:
-                onDataRoamingOff();
-                break;
-
-            case DctConstants.EVENT_ROAMING_ON:
-            case DctConstants.EVENT_ROAMING_SETTING_CHANGE:
-                onDataRoamingOnOrSettingsChanged(msg.what);
-                break;
-
-            case DctConstants.EVENT_DEVICE_PROVISIONED_CHANGE:
-                // Update sharedPreference to false when exits new device provisioning, indicating
-                // no users modifications on the settings for new devices. Thus carrier specific
-                // default roaming settings can be applied for new devices till user modification.
-                final SharedPreferences sp = PreferenceManager
-                        .getDefaultSharedPreferences(mPhone.getContext());
-                if (!sp.contains(Phone.DATA_ROAMING_IS_USER_SETTING_KEY)) {
-                    sp.edit().putBoolean(Phone.DATA_ROAMING_IS_USER_SETTING_KEY, false).commit();
-                }
-                break;
-
-            case DctConstants.EVENT_NETWORK_STATUS_CHANGED:
-                int status = msg.arg1;
-                int cid = msg.arg2;
-                String url = (String) msg.obj;
-                onNetworkStatusChanged(status, cid, url);
-                break;
-
-            case DctConstants.EVENT_RADIO_AVAILABLE:
-                onRadioAvailable();
-                break;
-
-            case DctConstants.EVENT_RADIO_OFF_OR_NOT_AVAILABLE:
-                onRadioOffOrNotAvailable();
-                break;
-
-            case DctConstants.EVENT_DATA_SETUP_COMPLETE:
-                ar = (AsyncResult) msg.obj;
-                pair = (Pair<ApnContext, Integer>) ar.userObj;
-                apnContext = pair.first;
-                generation = pair.second;
-                requestType = msg.arg1;
-                handoverFailureMode = msg.arg2;
-                if (apnContext.getConnectionGeneration() == generation) {
-                    boolean success = true;
-                    int cause = DataFailCause.UNKNOWN;
-                    if (ar.exception != null) {
-                        success = false;
-                        cause = (int) ar.result;
-                    }
-                    onDataSetupComplete(apnContext, success, cause, requestType,
-                            handoverFailureMode);
-                } else {
-                    loge("EVENT_DATA_SETUP_COMPLETE: Dropped the event because generation "
-                            + "did not match.");
-                }
-                break;
-
-            case DctConstants.EVENT_DATA_SETUP_COMPLETE_ERROR:
-                ar = (AsyncResult) msg.obj;
-                pair = (Pair<ApnContext, Integer>) ar.userObj;
-                apnContext = pair.first;
-                generation = pair.second;
-                handoverFailureMode = msg.arg2;
-                if (apnContext.getConnectionGeneration() == generation) {
-                    onDataSetupCompleteError(apnContext, handoverFailureMode, false);
-                } else {
-                    loge("EVENT_DATA_SETUP_COMPLETE_ERROR: Dropped the event because generation "
-                            + "did not match.");
-                }
-                break;
-
-            case DctConstants.EVENT_DISCONNECT_DONE:
-                log("EVENT_DISCONNECT_DONE msg=" + msg);
-                ar = (AsyncResult) msg.obj;
-                pair = (Pair<ApnContext, Integer>) ar.userObj;
-                apnContext = pair.first;
-                generation = pair.second;
-                if (apnContext.getConnectionGeneration() == generation) {
-                    onDisconnectDone(apnContext);
-                } else {
-                    loge("EVENT_DISCONNECT_DONE: Dropped the event because generation "
-                            + "did not match.");
-                }
-                break;
-
-            case DctConstants.EVENT_VOICE_CALL_STARTED:
-                onVoiceCallStarted();
-                break;
-
-            case DctConstants.EVENT_VOICE_CALL_ENDED:
-                onVoiceCallEnded();
-                break;
-            case DctConstants.CMD_SET_ENABLE_FAIL_FAST_MOBILE_DATA: {
-                sEnableFailFastRefCounter += (msg.arg1 == DctConstants.ENABLED) ? 1 : -1;
-                if (DBG) {
-                    log("CMD_SET_ENABLE_FAIL_FAST_MOBILE_DATA: "
-                            + " sEnableFailFastRefCounter=" + sEnableFailFastRefCounter);
-                }
-                if (sEnableFailFastRefCounter < 0) {
-                    final String s = "CMD_SET_ENABLE_FAIL_FAST_MOBILE_DATA: "
-                            + "sEnableFailFastRefCounter:" + sEnableFailFastRefCounter + " < 0";
-                    loge(s);
-                    sEnableFailFastRefCounter = 0;
-                }
-                final boolean enabled = sEnableFailFastRefCounter > 0;
-                if (DBG) {
-                    log("CMD_SET_ENABLE_FAIL_FAST_MOBILE_DATA: enabled=" + enabled
-                            + " sEnableFailFastRefCounter=" + sEnableFailFastRefCounter);
-                }
-                if (mFailFast != enabled) {
-                    mFailFast = enabled;
-
-                    mDataStallNoRxEnabled = !enabled;
-                    if (mDsRecoveryHandler.isNoRxDataStallDetectionEnabled()
-                            && isAnyDataConnected()
-                            && (!mInVoiceCall ||
-                                    mPhone.getServiceStateTracker()
-                                        .isConcurrentVoiceAndDataAllowed())) {
-                        if (DBG) log("CMD_SET_ENABLE_FAIL_FAST_MOBILE_DATA: start data stall");
-                        stopDataStallAlarm();
-                        startDataStallAlarm(DATA_STALL_NOT_SUSPECTED);
-                    } else {
-                        if (DBG) log("CMD_SET_ENABLE_FAIL_FAST_MOBILE_DATA: stop data stall");
-                        stopDataStallAlarm();
-                    }
-                }
-
-                break;
-            }
-            case DctConstants.CMD_ENABLE_MOBILE_PROVISIONING: {
-                Bundle bundle = msg.getData();
-                if (bundle != null) {
-                    try {
-                        mProvisioningUrl = (String)bundle.get(DctConstants.PROVISIONING_URL_KEY);
-                    } catch(ClassCastException e) {
-                        loge("CMD_ENABLE_MOBILE_PROVISIONING: provisioning url not a string" + e);
-                        mProvisioningUrl = null;
-                    }
-                }
-                if (TextUtils.isEmpty(mProvisioningUrl)) {
-                    loge("CMD_ENABLE_MOBILE_PROVISIONING: provisioning url is empty, ignoring");
-                    mIsProvisioning = false;
-                    mProvisioningUrl = null;
-                } else {
-                    loge("CMD_ENABLE_MOBILE_PROVISIONING: provisioningUrl=" + mProvisioningUrl);
-                    mIsProvisioning = true;
-                    startProvisioningApnAlarm();
-                }
-                break;
-            }
-            case DctConstants.EVENT_PROVISIONING_APN_ALARM: {
-                if (DBG) log("EVENT_PROVISIONING_APN_ALARM");
-                ApnContext apnCtx = mApnContextsByType.get(ApnSetting.TYPE_DEFAULT);
-                if (apnCtx.isProvisioningApn() && apnCtx.isConnectedOrConnecting()) {
-                    if (mProvisioningApnAlarmTag == msg.arg1) {
-                        if (DBG) log("EVENT_PROVISIONING_APN_ALARM: Disconnecting");
-                        mIsProvisioning = false;
-                        mProvisioningUrl = null;
-                        stopProvisioningApnAlarm();
-                        cleanUpConnectionInternal(true, RELEASE_TYPE_DETACH, apnCtx);
-                    } else {
-                        if (DBG) {
-                            log("EVENT_PROVISIONING_APN_ALARM: ignore stale tag,"
-                                    + " mProvisioningApnAlarmTag:" + mProvisioningApnAlarmTag
-                                    + " != arg1:" + msg.arg1);
-                        }
-                    }
-                } else {
-                    if (DBG) log("EVENT_PROVISIONING_APN_ALARM: Not connected ignore");
-                }
-                break;
-            }
-            case DctConstants.CMD_IS_PROVISIONING_APN: {
-                if (DBG) log("CMD_IS_PROVISIONING_APN");
-                boolean isProvApn;
-                try {
-                    String apnType = null;
-                    Bundle bundle = msg.getData();
-                    if (bundle != null) {
-                        apnType = (String)bundle.get(DctConstants.APN_TYPE_KEY);
-                    }
-                    if (TextUtils.isEmpty(apnType)) {
-                        loge("CMD_IS_PROVISIONING_APN: apnType is empty");
-                        isProvApn = false;
-                    } else {
-                        isProvApn = isProvisioningApn(apnType);
-                    }
-                } catch (ClassCastException e) {
-                    loge("CMD_IS_PROVISIONING_APN: NO provisioning url ignoring");
-                    isProvApn = false;
-                }
-                if (DBG) log("CMD_IS_PROVISIONING_APN: ret=" + isProvApn);
-                mReplyAc.replyToMessage(msg, DctConstants.CMD_IS_PROVISIONING_APN,
-                        isProvApn ? DctConstants.ENABLED : DctConstants.DISABLED);
-                break;
-            }
-            case DctConstants.EVENT_RESTART_RADIO: {
-                restartRadio();
-                break;
-            }
-            case DctConstants.CMD_NET_STAT_POLL: {
-                if (msg.arg1 == DctConstants.ENABLED) {
-                    handleStartNetStatPoll((DctConstants.Activity)msg.obj);
-                } else if (msg.arg1 == DctConstants.DISABLED) {
-                    handleStopNetStatPoll((DctConstants.Activity)msg.obj);
-                }
-                break;
-            }
-            case DctConstants.EVENT_PCO_DATA_RECEIVED: {
-                handlePcoData((AsyncResult)msg.obj);
-                break;
-            }
-            case DctConstants.EVENT_DATA_RECONNECT:
-                if (DBG) {
-                    log("EVENT_DATA_RECONNECT: subId=" + msg.arg1 + ", type="
-                            + requestTypeToString(msg.arg2));
-                }
-                onDataReconnect((ApnContext) msg.obj, msg.arg1, msg.arg2);
-                break;
-            case DctConstants.EVENT_DATA_SERVICE_BINDING_CHANGED:
-                onDataServiceBindingChanged((Boolean) ((AsyncResult) msg.obj).result);
-                break;
-            case DctConstants.EVENT_DATA_ENABLED_CHANGED:
-                ar = (AsyncResult) msg.obj;
-                if (ar.result instanceof Pair) {
-                    Pair<Boolean, Integer> p = (Pair<Boolean, Integer>) ar.result;
-                    boolean enabled = p.first;
-                    int reason = p.second;
-                    onDataEnabledChanged(enabled, reason);
-                }
-                break;
-            case DctConstants.EVENT_DATA_ENABLED_OVERRIDE_RULES_CHANGED:
-                onDataEnabledOverrideRulesChanged();
-                break;
-            case DctConstants.EVENT_NR_TIMER_WATCHDOG:
-                mWatchdog = false;
-                reevaluateUnmeteredConnections();
-                break;
-            case DctConstants.EVENT_TELEPHONY_DISPLAY_INFO_CHANGED:
-                reevaluateCongestedConnections();
-                reevaluateUnmeteredConnections();
-                break;
-            case DctConstants.EVENT_CARRIER_CONFIG_CHANGED:
-                onCarrierConfigChanged();
-                break;
-            case DctConstants.EVENT_SIM_STATE_UPDATED:
-                int simState = msg.arg1;
-                onSimStateUpdated(simState);
-                break;
-            case DctConstants.EVENT_APN_UNTHROTTLED:
-                ar = (AsyncResult) msg.obj;
-                String apn = (String) ar.result;
-                onApnUnthrottled(apn);
-                break;
-            case DctConstants.EVENT_TRAFFIC_DESCRIPTORS_UPDATED:
-                onTrafficDescriptorsUpdated();
-                break;
-            default:
-                Rlog.e("DcTracker", "Unhandled event=" + msg);
-                break;
-
-        }
-    }
-
-    private int getApnProfileID(String apnType) {
-        if (TextUtils.equals(apnType, ApnSetting.TYPE_IMS_STRING)) {
-            return RILConstants.DATA_PROFILE_IMS;
-        } else if (TextUtils.equals(apnType, ApnSetting.TYPE_FOTA_STRING)) {
-            return RILConstants.DATA_PROFILE_FOTA;
-        } else if (TextUtils.equals(apnType, ApnSetting.TYPE_CBS_STRING)) {
-            return RILConstants.DATA_PROFILE_CBS;
-        } else if (TextUtils.equals(apnType, ApnSetting.TYPE_IA_STRING)) {
-            return RILConstants.DATA_PROFILE_DEFAULT; // DEFAULT for now
-        } else if (TextUtils.equals(apnType, ApnSetting.TYPE_DUN_STRING)) {
-            return RILConstants.DATA_PROFILE_TETHERED;
-        } else {
-            return RILConstants.DATA_PROFILE_DEFAULT;
-        }
-    }
-
-    private int getCellLocationId() {
-        int cid = -1;
-        CellLocation loc = mPhone.getCurrentCellIdentity().asCellLocation();
-
-        if (loc != null) {
-            if (loc instanceof GsmCellLocation) {
-                cid = ((GsmCellLocation)loc).getCid();
-            } else if (loc instanceof CdmaCellLocation) {
-                cid = ((CdmaCellLocation)loc).getBaseStationId();
-            }
-        }
-        return cid;
-    }
-
-    /**
-     * Update link bandwidth estimate default values from carrier config.
-     * @param bandwidths String array of "RAT:upstream,downstream" for each RAT
-     * @param useLte For NR NSA, whether to use LTE value for upstream or not
-     */
-    private void updateLinkBandwidths(String[] bandwidths, boolean useLte) {
-        ConcurrentHashMap<String, Pair<Integer, Integer>> temp = new ConcurrentHashMap<>();
-        for (String config : bandwidths) {
-            int downstream = 14;
-            int upstream = 14;
-            String[] kv = config.split(":");
-            if (kv.length == 2) {
-                String[] split = kv[1].split(",");
-                if (split.length == 2) {
-                    try {
-                        downstream = Integer.parseInt(split[0]);
-                        upstream = Integer.parseInt(split[1]);
-                    } catch (NumberFormatException ignored) {
-                    }
-                }
-                temp.put(kv[0], new Pair<>(downstream, upstream));
-            }
-        }
-        if (useLte) {
-            Pair<Integer, Integer> ltePair =
-                    temp.get(DataConfigManager.DATA_CONFIG_NETWORK_TYPE_LTE);
-            if (ltePair != null) {
-                if (temp.containsKey(DataConfigManager.DATA_CONFIG_NETWORK_TYPE_NR_NSA)) {
-                    temp.put(DataConfigManager.DATA_CONFIG_NETWORK_TYPE_NR_NSA, new Pair<>(
-                            temp.get(DataConfigManager.DATA_CONFIG_NETWORK_TYPE_NR_NSA).first,
-                            ltePair.second));
-                }
-                if (temp.containsKey(DataConfigManager.DATA_CONFIG_NETWORK_TYPE_NR_NSA_MMWAVE)) {
-                    temp.put(DataConfigManager.DATA_CONFIG_NETWORK_TYPE_NR_NSA_MMWAVE, new Pair<>(
-                            temp.get(DataConfigManager.DATA_CONFIG_NETWORK_TYPE_NR_NSA_MMWAVE)
-                                    .first, ltePair.second));
-                }
-            }
-        }
-        mBandwidths = temp;
-        for (DataConnection dc : mDataConnections.values()) {
-            dc.sendMessage(DataConnection.EVENT_CARRIER_CONFIG_LINK_BANDWIDTHS_CHANGED);
-        }
-    }
-
-    /**
-     * Return the link upstream/downstream values from CarrierConfig for the given RAT name.
-     * @param ratName RAT name from ServiceState#rilRadioTechnologyToString.
-     * @return pair of downstream/upstream values (kbps), or null if the config is not defined.
-     */
-    public Pair<Integer, Integer> getLinkBandwidthsFromCarrierConfig(String ratName) {
-        return mBandwidths.get(ratName);
-    }
-
-    @VisibleForTesting
-    public boolean shouldAutoAttach() {
-        if (mAutoAttachEnabled.get()) return true;
-
-        PhoneSwitcher phoneSwitcher = PhoneSwitcher.getInstance();
-        ServiceState serviceState = mPhone.getServiceState();
-
-        if (phoneSwitcher == null || serviceState == null) return false;
-
-        // If voice is also not in service, don't auto attach.
-        if (serviceState.getState() != ServiceState.STATE_IN_SERVICE) return false;
-
-        // If voice is on LTE or NR, don't auto attach as for LTE / NR data would be attached.
-        if (serviceState.getVoiceNetworkType() == NETWORK_TYPE_LTE
-                || serviceState.getVoiceNetworkType() == NETWORK_TYPE_NR) return false;
-
-        // If phone is non default phone, modem may have detached from data for optimization.
-        // If phone is in voice call, for DSDS case DDS switch may be limited so we do try our
-        // best to setup data connection and allow auto-attach.
-        return (mPhone.getPhoneId() != phoneSwitcher.getPreferredDataPhoneId()
-                || mPhone.getState() != PhoneConstants.State.IDLE);
-    }
-
-    private void notifyAllDataDisconnected() {
-        sEnableFailFastRefCounter = 0;
-        mFailFast = false;
-        log("notify all data disconnected");
-        mAllDataDisconnectedRegistrants.notifyRegistrants();
-    }
-
-    public void registerForAllDataDisconnected(Handler h, int what) {
-        mAllDataDisconnectedRegistrants.addUnique(h, what, null);
-
-        if (areAllDataDisconnected()) {
-            notifyAllDataDisconnected();
-        }
-    }
-
-    public void unregisterForAllDataDisconnected(Handler h) {
-        mAllDataDisconnectedRegistrants.remove(h);
-    }
-
-    private void onDataEnabledChanged(boolean enable,
-                                      @DataEnabledChangedReason int enabledChangedReason) {
-        if (DBG) {
-            log("onDataEnabledChanged: enable=" + enable + ", enabledChangedReason="
-                    + enabledChangedReason);
-        }
-
-        if (enable) {
-            reevaluateDataConnections();
-            setupDataOnAllConnectableApns(Phone.REASON_DATA_ENABLED, RetryFailures.ALWAYS);
-        } else {
-            String cleanupReason;
-            switch (enabledChangedReason) {
-                case DataEnabledSettings.REASON_INTERNAL_DATA_ENABLED:
-                    cleanupReason = Phone.REASON_DATA_DISABLED_INTERNAL;
-                    break;
-                case DataEnabledSettings.REASON_DATA_ENABLED_BY_CARRIER:
-                    cleanupReason = Phone.REASON_CARRIER_ACTION_DISABLE_METERED_APN;
-                    break;
-                case DataEnabledSettings.REASON_USER_DATA_ENABLED:
-                case DataEnabledSettings.REASON_POLICY_DATA_ENABLED:
-                case DataEnabledSettings.REASON_PROVISIONED_CHANGED:
-                case DataEnabledSettings.REASON_PROVISIONING_DATA_ENABLED_CHANGED:
-                default:
-                    cleanupReason = Phone.REASON_DATA_SPECIFIC_DISABLED;
-                    break;
-
-            }
-            cleanUpAllConnectionsInternal(true, cleanupReason);
-        }
-    }
-
-    private void reevaluateCongestedConnections() {
-        log("reevaluateCongestedConnections");
-        int rat = mPhone.getDisplayInfoController().getTelephonyDisplayInfo().getNetworkType();
-        // congested override and either network is specified or unknown and all networks specified
-        boolean isCongested = mCongestedOverride && (mCongestedNetworkTypes.contains(rat)
-                || mCongestedNetworkTypes.containsAll(Arrays.stream(
-                TelephonyManager.getAllNetworkTypes()).boxed().collect(Collectors.toSet())));
-        for (DataConnection dataConnection : mDataConnections.values()) {
-            dataConnection.onCongestednessChanged(isCongested);
-        }
-    }
-
-    private void reevaluateUnmeteredConnections() {
-        log("reevaluateUnmeteredConnections");
-        int rat = mPhone.getDisplayInfoController().getTelephonyDisplayInfo().getNetworkType();
-        if (isNrUnmetered() && (!mPhone.getServiceState().getRoaming() || mNrNsaRoamingUnmetered)) {
-            setDataConnectionUnmetered(true);
-            if (!mWatchdog) {
-                startWatchdogAlarm();
-            }
-        } else {
-            stopWatchdogAlarm();
-            setDataConnectionUnmetered(isNetworkTypeUnmetered(rat));
-        }
-    }
-
-    private void setDataConnectionUnmetered(boolean isUnmetered) {
-        if (!isUnmetered || isTempNotMeteredSupportedByCarrier()) {
-            for (DataConnection dataConnection : mDataConnections.values()) {
-                dataConnection.onMeterednessChanged(isUnmetered);
-            }
-        }
-    }
-
-    private boolean isNetworkTypeUnmetered(@NetworkType int networkType) {
-        boolean isUnmetered;
-        if (mUnmeteredNetworkTypes == null || !mUnmeteredOverride) {
-            // check SubscriptionPlans if override is not defined
-            isUnmetered = isNetworkTypeUnmeteredViaSubscriptionPlan(networkType);
-            log("isNetworkTypeUnmeteredViaSubscriptionPlan: networkType=" + networkType
-                    + ", isUnmetered=" + isUnmetered);
-            return isUnmetered;
-        }
-        // unmetered override and either network is specified or unknown and all networks specified
-        isUnmetered = mUnmeteredNetworkTypes.contains(networkType)
-                || mUnmeteredNetworkTypes.containsAll(Arrays.stream(
-                TelephonyManager.getAllNetworkTypes()).boxed().collect(Collectors.toSet()));
-        if (DBG) {
-            log("isNetworkTypeUnmetered: networkType=" + networkType
-                    + ", isUnmetered=" + isUnmetered);
-        }
-        return isUnmetered;
-    }
-
-    private boolean isNetworkTypeUnmeteredViaSubscriptionPlan(@NetworkType int networkType) {
-        if (mSubscriptionPlans.isEmpty()) {
-            // safe return false if unable to get subscription plans or plans don't exist
-            return false;
-        }
-
-        boolean isGeneralUnmetered = true;
-        Set<Integer> allNetworkTypes = Arrays.stream(TelephonyManager.getAllNetworkTypes())
-                .boxed().collect(Collectors.toSet());
-        for (SubscriptionPlan plan : mSubscriptionPlans) {
-            // check plan is general (applies to all network types) or specific
-            if (Arrays.stream(plan.getNetworkTypes()).boxed().collect(Collectors.toSet())
-                    .containsAll(allNetworkTypes)) {
-                if (!isPlanUnmetered(plan)) {
-                    // metered takes precedence over unmetered for safety
-                    isGeneralUnmetered = false;
-                }
-            } else {
-                // check plan applies to given network type
-                if (networkType != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
-                    for (int planNetworkType : plan.getNetworkTypes()) {
-                        if (planNetworkType == networkType) {
-                            return isPlanUnmetered(plan);
-                        }
-                    }
-                }
-            }
-        }
-        return isGeneralUnmetered;
-    }
-
-    private boolean isPlanUnmetered(SubscriptionPlan plan) {
-        return plan.getDataLimitBytes() == SubscriptionPlan.BYTES_UNLIMITED;
-    }
-
-    private boolean isNrUnmetered() {
-        int rat = mPhone.getDisplayInfoController().getTelephonyDisplayInfo().getNetworkType();
-        int override = mPhone.getDisplayInfoController().getTelephonyDisplayInfo()
-                .getOverrideNetworkType();
-
-        if (isNetworkTypeUnmetered(NETWORK_TYPE_NR)) {
-            if (mNrNsaMmwaveUnmetered) {
-                if (override == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED) {
-                    if (DBG) log("NR unmetered for mmwave only");
-                    return true;
-                }
-                return false;
-            } else if (mNrNsaSub6Unmetered) {
-                if (override == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA) {
-                    if (DBG) log("NR unmetered for sub6 only");
-                    return true;
-                }
-                return false;
-            }
-            if (override == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED
-                    || override == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA
-                    || rat == NETWORK_TYPE_NR) {
-                if (DBG) log("NR unmetered for all frequencies");
-                return true;
-            }
-            return false;
-        }
-
-        if (mNrNsaAllUnmetered) {
-            if (mNrNsaMmwaveUnmetered) {
-                if (override == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED) {
-                    if (DBG) log("NR NSA unmetered for mmwave only via carrier configs");
-                    return true;
-                }
-                return false;
-            } else if (mNrNsaSub6Unmetered) {
-                if (override == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA) {
-                    if (DBG) log("NR NSA unmetered for sub6 only via carrier configs");
-                    return true;
-                }
-                return false;
-            }
-            if (override == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED
-                    || override == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA) {
-                if (DBG) log("NR NSA unmetered for all frequencies via carrier configs");
-                return true;
-            }
-            return false;
-        }
-
-        if (mNrSaAllUnmetered) {
-            // TODO: add logic for mNrSaMmwaveUnmetered and mNrSaSub6Unmetered once it's defined
-            // in TelephonyDisplayInfo
-            if (rat == NETWORK_TYPE_NR) {
-                if (DBG) log("NR SA unmetered for all frequencies via carrier configs");
-                return true;
-            }
-            return false;
-        }
-
-        return false;
-    }
-
-    private boolean isTempNotMeteredSupportedByCarrier() {
-        CarrierConfigManager configManager =
-                mPhone.getContext().getSystemService(CarrierConfigManager.class);
-        if (configManager != null) {
-            PersistableBundle bundle = configManager.getConfigForSubId(mPhone.getSubId());
-            if (bundle != null) {
-                return bundle.getBoolean(
-                        CarrierConfigManager.KEY_NETWORK_TEMP_NOT_METERED_SUPPORTED_BOOL);
-            }
-        }
-
-        return false;
-    }
-
-    protected void log(String s) {
-        Rlog.d(mLogTag, s);
-    }
-
-    private void loge(String s) {
-        Rlog.e(mLogTag, s);
-    }
-
-    private void logSortedApnContexts() {
-        if (VDBG) {
-            log("initApnContexts: X mApnContexts=" + mApnContexts);
-
-            StringBuilder sb = new StringBuilder();
-            sb.append("sorted apncontexts -> [");
-            for (ApnContext apnContext : mPrioritySortedApnContexts) {
-                sb.append(apnContext);
-                sb.append(", ");
-
-                log("sorted list");
-            }
-            sb.append("]");
-            log(sb.toString());
-        }
-    }
-
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("DcTracker:");
-        pw.println(" RADIO_TESTS=" + RADIO_TESTS);
-        pw.println(" mDataEnabledSettings=" + mDataEnabledSettings);
-        pw.println(" isDataAllowed=" + isDataAllowed(null));
-        pw.flush();
-        pw.println(" mRequestedApnType=" + mRequestedApnType);
-        pw.println(" mPhone=" + mPhone.getPhoneName());
-        pw.println(" mConfigReady=" + mConfigReady);
-        pw.println(" mSimState=" + SubscriptionInfoUpdater.simStateString(mSimState));
-        pw.println(" mActivity=" + mActivity);
-        pw.println(" mState=" + mState);
-        pw.println(" mTxPkts=" + mTxPkts);
-        pw.println(" mRxPkts=" + mRxPkts);
-        pw.println(" mNetStatPollPeriod=" + mNetStatPollPeriod);
-        pw.println(" mNetStatPollEnabled=" + mNetStatPollEnabled);
-        pw.println(" mDataStallTxRxSum=" + mDataStallTxRxSum);
-        pw.println(" mDataStallAlarmTag=" + mDataStallAlarmTag);
-        pw.println(" mDataStallNoRxEnabled=" + mDataStallNoRxEnabled);
-        pw.println(" mEmergencyApn=" + mEmergencyApn);
-        pw.println(" mSentSinceLastRecv=" + mSentSinceLastRecv);
-        pw.println(" mNoRecvPollCount=" + mNoRecvPollCount);
-        pw.println(" mResolver=" + mResolver);
-        pw.println(" mReconnectIntent=" + mReconnectIntent);
-        pw.println(" mAutoAttachEnabled=" + mAutoAttachEnabled.get());
-        pw.println(" mIsScreenOn=" + mIsScreenOn);
-        pw.println(" mUniqueIdGenerator=" + mUniqueIdGenerator);
-        pw.println(" mDataServiceBound=" + mDataServiceBound);
-        pw.println(" mDataRoamingLeakageLog= ");
-        mDataRoamingLeakageLog.dump(fd, pw, args);
-        pw.println(" mApnSettingsInitializationLog= ");
-        mApnSettingsInitializationLog.dump(fd, pw, args);
-        pw.flush();
-        pw.println(" ***************************************");
-        DcController dcc = mDcc;
-        if (dcc != null) {
-            if (mDataServiceBound) {
-                dcc.dump(fd, pw, args);
-            } else {
-                pw.println(" Can't dump mDcc because data service is not bound.");
-            }
-        } else {
-            pw.println(" mDcc=null");
-        }
-        pw.println(" ***************************************");
-        HashMap<Integer, DataConnection> dcs = mDataConnections;
-        if (dcs != null) {
-            Set<Entry<Integer, DataConnection> > mDcSet = mDataConnections.entrySet();
-            pw.println(" mDataConnections: count=" + mDcSet.size());
-            for (Entry<Integer, DataConnection> entry : mDcSet) {
-                pw.printf(" *** mDataConnection[%d] \n", entry.getKey());
-                entry.getValue().dump(fd, pw, args);
-            }
-        } else {
-            pw.println("mDataConnections=null");
-        }
-        pw.println(" ***************************************");
-        pw.flush();
-        HashMap<String, Integer> apnToDcId = mApnToDataConnectionId;
-        if (apnToDcId != null) {
-            Set<Entry<String, Integer>> apnToDcIdSet = apnToDcId.entrySet();
-            pw.println(" mApnToDataConnectonId size=" + apnToDcIdSet.size());
-            for (Entry<String, Integer> entry : apnToDcIdSet) {
-                pw.printf(" mApnToDataConnectonId[%s]=%d\n", entry.getKey(), entry.getValue());
-            }
-        } else {
-            pw.println("mApnToDataConnectionId=null");
-        }
-        pw.println(" ***************************************");
-        pw.flush();
-        ConcurrentHashMap<String, ApnContext> apnCtxs = mApnContexts;
-        if (apnCtxs != null) {
-            Set<Entry<String, ApnContext>> apnCtxsSet = apnCtxs.entrySet();
-            pw.println(" mApnContexts size=" + apnCtxsSet.size());
-            for (Entry<String, ApnContext> entry : apnCtxsSet) {
-                entry.getValue().dump(fd, pw, args);
-            }
-            ApnContext.dumpLocalLog(fd, pw, args);
-            pw.println(" ***************************************");
-        } else {
-            pw.println(" mApnContexts=null");
-        }
-        pw.flush();
-
-        pw.println(" mAllApnSettings size=" + mAllApnSettings.size());
-        for (int i = 0; i < mAllApnSettings.size(); i++) {
-            pw.printf(" mAllApnSettings[%d]: %s\n", i, mAllApnSettings.get(i));
-        }
-        pw.flush();
-
-        pw.println(" mPreferredApn=" + mPreferredApn);
-        pw.println(" mIsPsRestricted=" + mIsPsRestricted);
-        pw.println(" mIsDisposed=" + mIsDisposed);
-        pw.println(" mIntentReceiver=" + mIntentReceiver);
-        pw.println(" mReregisterOnReconnectFailure=" + mReregisterOnReconnectFailure);
-        pw.println(" canSetPreferApn=" + mCanSetPreferApn);
-        pw.println(" mApnObserver=" + mApnObserver);
-        pw.println(" isAnyDataConnected=" + isAnyDataConnected());
-        pw.println(" mAttached=" + mAttached.get());
-        mDataEnabledSettings.dump(fd, pw, args);
-        pw.flush();
-    }
-
-    public String[] getPcscfAddress(String apnType) {
-        log("getPcscfAddress()");
-        ApnContext apnContext = null;
-
-        if(apnType == null){
-            log("apnType is null, return null");
-            return null;
-        }
-
-        if (TextUtils.equals(apnType, ApnSetting.TYPE_EMERGENCY_STRING)) {
-            apnContext = mApnContextsByType.get(ApnSetting.TYPE_EMERGENCY);
-        } else if (TextUtils.equals(apnType, ApnSetting.TYPE_IMS_STRING)) {
-            apnContext = mApnContextsByType.get(ApnSetting.TYPE_IMS);
-        } else {
-            log("apnType is invalid, return null");
-            return null;
-        }
-
-        if (apnContext == null) {
-            log("apnContext is null, return null");
-            return null;
-        }
-
-        DataConnection dataConnection = apnContext.getDataConnection();
-        String[] result = null;
-
-        if (dataConnection != null) {
-            result = dataConnection.getPcscfAddresses();
-
-            if (result != null) {
-                for (int i = 0; i < result.length; i++) {
-                    log("Pcscf[" + i + "]: " + result[i]);
-                }
-            }
-            return result;
-        }
-        return null;
-    }
-
-    /**
-     * Create default apn settings for the apn type like emergency, and ims
-     */
-    private ApnSetting buildDefaultApnSetting(@NonNull String entry,
-            @NonNull String apn, @ApnType int apnTypeBitmask) {
-        return new ApnSetting.Builder()
-                .setEntryName(entry)
-                .setProtocol(ApnSetting.PROTOCOL_IPV4V6)
-                .setRoamingProtocol(ApnSetting.PROTOCOL_IPV4V6)
-                .setApnName(apn)
-                .setApnTypeBitmask(apnTypeBitmask)
-                .setCarrierEnabled(true)
-                .setApnSetId(Telephony.Carriers.MATCH_ALL_APN_SET_ID)
-                .build();
-    }
-
-    /**
-     * Add default APN settings to APN settings list as needed
-     */
-    private void addDefaultApnSettingsAsNeeded() {
-        boolean isEmergencyApnConfigured = false;
-        boolean isImsApnConfigured = false;
-
-        for (ApnSetting apn : mAllApnSettings) {
-            if (apn.canHandleType(ApnSetting.TYPE_EMERGENCY)) {
-                isEmergencyApnConfigured = true;
-            }
-            if (apn.canHandleType(ApnSetting.TYPE_IMS)) {
-                isImsApnConfigured = true;
-            }
-            if (isEmergencyApnConfigured && isImsApnConfigured) {
-                log("Both emergency and ims apn setting are already present");
-                return;
-            }
-        }
-
-        // Add default apn setting for emergency service if it is not present
-        if (!isEmergencyApnConfigured) {
-            mAllApnSettings.add(buildDefaultApnSetting(
-                    "DEFAULT EIMS", "sos", ApnSetting.TYPE_EMERGENCY));
-            log("default emergency apn is created");
-        }
-
-        // Only add default apn setting for ims when it is not present and sim is loaded
-        if (!isImsApnConfigured && mSimState == TelephonyManager.SIM_STATE_LOADED) {
-            mAllApnSettings.add(buildDefaultApnSetting(
-                    "DEFAULT IMS", "ims", ApnSetting.TYPE_IMS));
-            log("default ims apn is created");
-        }
-    }
-
-    private void cleanUpConnectionsOnUpdatedApns(boolean detach, String reason) {
-        if (DBG) log("cleanUpConnectionsOnUpdatedApns: detach=" + detach);
-        if (mAllApnSettings.isEmpty()) {
-            cleanUpAllConnectionsInternal(detach, Phone.REASON_APN_CHANGED);
-        } else {
-            if (getDataRat() == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN) {
-                // unknown rat is an exception for data rat change. Its only received when out of
-                // service and is not applicable for apn bearer bitmask. We should bypass the check
-                // of waiting apn list and keep the data connection on.
-                return;
-            }
-            for (ApnContext apnContext : mApnContexts.values()) {
-                boolean cleanupRequired = true;
-                if (!apnContext.isDisconnected()) {
-                    ArrayList<ApnSetting> waitingApns = buildWaitingApns(
-                            apnContext.getApnType(), getDataRat());
-                    if (apnContext.getWaitingApns().size() != waitingApns.size()
-                            || !apnContext.getWaitingApns().containsAll(waitingApns)) {
-                        apnContext.setWaitingApns(waitingApns);
-                    }
-                    for (ApnSetting apnSetting : waitingApns) {
-                        if (areCompatible(apnSetting, apnContext.getApnSetting())) {
-                            cleanupRequired = false;
-                            break;
-                        }
-                    }
-
-                    if (cleanupRequired) {
-                        if (DBG) {
-                            log("cleanUpConnectionsOnUpdatedApns: APN type "
-                                    + apnContext.getApnType() + " clean up is required. The new "
-                                    + "waiting APN list " + waitingApns + " does not cover "
-                                    + apnContext.getApnSetting());
-                        }
-                        apnContext.setReason(reason);
-                        cleanUpConnectionInternal(true, RELEASE_TYPE_DETACH, apnContext);
-                    }
-                }
-            }
-        }
-
-        if (!isAnyDataConnected()) {
-            stopNetStatPoll();
-            stopDataStallAlarm();
-        }
-
-        mRequestedApnType = ApnSetting.TYPE_DEFAULT;
-
-        if (areAllDataDisconnected()) {
-            notifyAllDataDisconnected();
-        }
-    }
-
-    /**
-     * Polling stuff
-     */
-    protected void resetPollStats() {
-        mTxPkts = -1;
-        mRxPkts = -1;
-        mNetStatPollPeriod = POLL_NETSTAT_MILLIS;
-    }
-
-    protected void startNetStatPoll() {
-        if (isAnyDataConnected() && !mNetStatPollEnabled) {
-            if (DBG) {
-                log("startNetStatPoll");
-            }
-            resetPollStats();
-            mNetStatPollEnabled = true;
-            mPollNetStat.run();
-        }
-        if (mPhone != null) {
-            mPhone.notifyDataActivity();
-        }
-    }
-
-    protected void stopNetStatPoll() {
-        mNetStatPollEnabled = false;
-        removeCallbacks(mPollNetStat);
-        if (DBG) {
-            log("stopNetStatPoll");
-        }
-
-        // To sync data activity icon in the case of switching data connection to send MMS.
-        if (mPhone != null) {
-            mPhone.notifyDataActivity();
-        }
-    }
-
-    public void sendStartNetStatPoll(DctConstants.Activity activity) {
-        Message msg = obtainMessage(DctConstants.CMD_NET_STAT_POLL);
-        msg.arg1 = DctConstants.ENABLED;
-        msg.obj = activity;
-        sendMessage(msg);
-    }
-
-    private void handleStartNetStatPoll(DctConstants.Activity activity) {
-        startNetStatPoll();
-        startDataStallAlarm(DATA_STALL_NOT_SUSPECTED);
-        setActivity(activity);
-    }
-
-    public void sendStopNetStatPoll(DctConstants.Activity activity) {
-        Message msg = obtainMessage(DctConstants.CMD_NET_STAT_POLL);
-        msg.arg1 = DctConstants.DISABLED;
-        msg.obj = activity;
-        sendMessage(msg);
-    }
-
-    private void handleStopNetStatPoll(DctConstants.Activity activity) {
-        stopNetStatPoll();
-        stopDataStallAlarm();
-        setActivity(activity);
-    }
-
-    private void onDataEnabledOverrideRulesChanged() {
-        if (DBG) {
-            log("onDataEnabledOverrideRulesChanged");
-        }
-
-        for (ApnContext apnContext : mPrioritySortedApnContexts) {
-            if (isDataAllowed(apnContext, REQUEST_TYPE_NORMAL, null)) {
-                if (apnContext.getDataConnection() != null) {
-                    apnContext.getDataConnection().reevaluateRestrictedState();
-                }
-                setupDataOnConnectableApn(apnContext, Phone.REASON_DATA_ENABLED_OVERRIDE,
-                        RetryFailures.ALWAYS);
-            } else if (shouldCleanUpConnection(apnContext, true, false)) {
-                apnContext.setReason(Phone.REASON_DATA_ENABLED_OVERRIDE);
-                cleanUpConnectionInternal(true, RELEASE_TYPE_DETACH, apnContext);
-            }
-        }
-    }
-
-    private void updateDataActivity() {
-        long sent, received;
-
-        DctConstants.Activity newActivity;
-
-        TxRxSum preTxRxSum = new TxRxSum(mTxPkts, mRxPkts);
-        TxRxSum curTxRxSum = new TxRxSum();
-        curTxRxSum.updateTotalTxRxSum();
-        mTxPkts = curTxRxSum.txPkts;
-        mRxPkts = curTxRxSum.rxPkts;
-
-        if (VDBG) {
-            log("updateDataActivity: curTxRxSum=" + curTxRxSum + " preTxRxSum=" + preTxRxSum);
-        }
-
-        if (mNetStatPollEnabled && (preTxRxSum.txPkts > 0 || preTxRxSum.rxPkts > 0)) {
-            sent = mTxPkts - preTxRxSum.txPkts;
-            received = mRxPkts - preTxRxSum.rxPkts;
-
-            if (VDBG)
-                log("updateDataActivity: sent=" + sent + " received=" + received);
-            if (sent > 0 && received > 0) {
-                newActivity = DctConstants.Activity.DATAINANDOUT;
-            } else if (sent > 0 && received == 0) {
-                newActivity = DctConstants.Activity.DATAOUT;
-            } else if (sent == 0 && received > 0) {
-                newActivity = DctConstants.Activity.DATAIN;
-            } else {
-                newActivity = (mActivity == DctConstants.Activity.DORMANT) ?
-                        mActivity : DctConstants.Activity.NONE;
-            }
-
-            if (mActivity != newActivity && mIsScreenOn) {
-                if (VDBG)
-                    log("updateDataActivity: newActivity=" + newActivity);
-                mActivity = newActivity;
-                mPhone.notifyDataActivity();
-            }
-        }
-    }
-
-    private void handlePcoData(AsyncResult ar) {
-        if (ar.exception != null) {
-            loge("PCO_DATA exception: " + ar.exception);
-            return;
-        }
-        PcoData pcoData = (PcoData)(ar.result);
-        ArrayList<DataConnection> dcList = new ArrayList<>();
-        DataConnection temp = mDcc.getActiveDcByCid(pcoData.cid);
-        if (temp != null) {
-            dcList.add(temp);
-        }
-        if (dcList.size() == 0) {
-            loge("PCO_DATA for unknown cid: " + pcoData.cid + ", inferring");
-            for (DataConnection dc : mDataConnections.values()) {
-                final int cid = dc.getCid();
-                if (cid == pcoData.cid) {
-                    if (VDBG) log("  found " + dc);
-                    dcList.clear();
-                    dcList.add(dc);
-                    break;
-                }
-                // check if this dc is still connecting
-                if (cid == -1) {
-                    for (ApnContext apnContext : dc.getApnContexts()) {
-                        if (apnContext.getState() == DctConstants.State.CONNECTING) {
-                            if (VDBG) log("  found potential " + dc);
-                            dcList.add(dc);
-                            break;
-                        }
-                    }
-                }
-            }
-        }
-        if (dcList.size() == 0) {
-            loge("PCO_DATA - couldn't infer cid");
-            return;
-        }
-        for (DataConnection dc : dcList) {
-            List<ApnContext> apnContextList = dc.getApnContexts();
-            if (apnContextList.size() == 0) {
-                break;
-            }
-            // send one out for each apn type in play
-            for (ApnContext apnContext : apnContextList) {
-                String apnType = apnContext.getApnType();
-
-                final Intent intent = new Intent(TelephonyManager.ACTION_CARRIER_SIGNAL_PCO_VALUE);
-                intent.putExtra(TelephonyManager.EXTRA_APN_TYPE,
-                        ApnSetting.getApnTypesBitmaskFromString(apnType));
-                intent.putExtra(TelephonyManager.EXTRA_APN_PROTOCOL,
-                        ApnSetting.getProtocolIntFromString(pcoData.bearerProto));
-                intent.putExtra(TelephonyManager.EXTRA_PCO_ID, pcoData.pcoId);
-                intent.putExtra(TelephonyManager.EXTRA_PCO_VALUE, pcoData.contents);
-                mPhone.getCarrierSignalAgent().notifyCarrierSignalReceivers(intent);
-            }
-        }
-    }
-
-    /**
-     * Data-Stall
-     */
-
-    // Recovery action taken in case of data stall
-    @IntDef(
-        value = {
-            RECOVERY_ACTION_GET_DATA_CALL_LIST,
-            RECOVERY_ACTION_CLEANUP,
-            RECOVERY_ACTION_REREGISTER,
-            RECOVERY_ACTION_RADIO_RESTART
-        })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface RecoveryAction {};
-    private static final int RECOVERY_ACTION_GET_DATA_CALL_LIST      = 0;
-    private static final int RECOVERY_ACTION_CLEANUP                 = 1;
-    private static final int RECOVERY_ACTION_REREGISTER              = 2;
-    private static final int RECOVERY_ACTION_RADIO_RESTART           = 3;
-
-    // Recovery handler class for cellular data stall
-    private class DataStallRecoveryHandler {
-        // Default minimum duration between each recovery steps
-        private static final int
-                DEFAULT_MIN_DURATION_BETWEEN_RECOVERY_STEPS_IN_MS = (3 * 60 * 1000); // 3 mins
-
-        // The elapsed real time of last recovery attempted
-        private long mTimeLastRecoveryStartMs;
-        // Whether current network good or not
-        private boolean mIsValidNetwork;
-        // Whether data stall happened or not.
-        private boolean mWasDataStall;
-        // Whether the result of last action(RADIO_RESTART) reported.
-        private boolean mLastActionReported;
-        // The real time for data stall start.
-        private long mDataStallStartMs;
-        // Last data stall action.
-        private @RecoveryAction int mLastAction;
-
-        public DataStallRecoveryHandler() {
-            reset();
-        }
-
-        public void reset() {
-            mTimeLastRecoveryStartMs = 0;
-            putRecoveryAction(RECOVERY_ACTION_GET_DATA_CALL_LIST);
-        }
-
-        private void setNetworkValidationState(boolean isValid) {
-            // Validation status is true and was not data stall.
-            if (isValid && !mWasDataStall) {
-                return;
-            }
-
-            if (!mWasDataStall) {
-                mWasDataStall = true;
-                mDataStallStartMs = SystemClock.elapsedRealtime();
-                if (DBG) log("data stall: start time = " + mDataStallStartMs);
-                return;
-            }
-
-            if (!mLastActionReported) {
-                int timeDuration = (int) (SystemClock.elapsedRealtime() - mDataStallStartMs);
-                if (DBG) {
-                    log("data stall: lastaction = " + mLastAction + ", isRecovered = "
-                            + isValid + ", mTimeDuration = " + timeDuration);
-                }
-                DataStallRecoveryStats.onDataStallEvent(mLastAction, mPhone, isValid,
-                                                        timeDuration);
-                mLastActionReported = true;
-            }
-
-            if (isValid) {
-                mLastActionReported = false;
-                mWasDataStall = false;
-            }
-        }
-
-        public boolean isAggressiveRecovery() {
-            @RecoveryAction int action = getRecoveryAction();
-
-            return ((action == RECOVERY_ACTION_CLEANUP)
-                    || (action == RECOVERY_ACTION_REREGISTER)
-                    || (action == RECOVERY_ACTION_RADIO_RESTART));
-        }
-
-        private long getMinDurationBetweenRecovery() {
-            return Settings.Global.getLong(mResolver,
-                Settings.Global.MIN_DURATION_BETWEEN_RECOVERY_STEPS_IN_MS,
-                DEFAULT_MIN_DURATION_BETWEEN_RECOVERY_STEPS_IN_MS);
-        }
-
-        private long getElapsedTimeSinceRecoveryMs() {
-            return (SystemClock.elapsedRealtime() - mTimeLastRecoveryStartMs);
-        }
-
-        @RecoveryAction
-        private int getRecoveryAction() {
-            @RecoveryAction int action = Settings.System.getInt(mResolver,
-                    "radio.data.stall.recovery.action", RECOVERY_ACTION_GET_DATA_CALL_LIST);
-            if (VDBG_STALL) log("getRecoveryAction: " + action);
-            return action;
-        }
-
-        private void putRecoveryAction(@RecoveryAction int action) {
-            Settings.System.putInt(mResolver, "radio.data.stall.recovery.action", action);
-            if (VDBG_STALL) log("putRecoveryAction: " + action);
-        }
-
-        private void broadcastDataStallDetected(@RecoveryAction int recoveryAction) {
-            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, READ_PRIVILEGED_PHONE_STATE);
-        }
-
-        private boolean isRecoveryAlreadyStarted() {
-            return getRecoveryAction() != RECOVERY_ACTION_GET_DATA_CALL_LIST;
-        }
-
-        private boolean checkRecovery() {
-            // To avoid back to back recovery wait for a grace period
-            if (getElapsedTimeSinceRecoveryMs() < getMinDurationBetweenRecovery()) {
-                if (VDBG_STALL) log("skip back to back data stall recovery");
-                return false;
-            }
-
-            // Skip recovery if it can cause a call to drop
-            if (mPhone.getState() != PhoneConstants.State.IDLE
-                    && getRecoveryAction() > RECOVERY_ACTION_CLEANUP) {
-                if (VDBG_STALL) log("skip data stall recovery as there is an active call");
-                return false;
-            }
-
-            // Allow recovery if data is expected to work
-            return mAttached.get() && isDataAllowed(null);
-        }
-
-        private void triggerRecovery() {
-            // Updating the recovery start time early to avoid race when
-            // the message is being processed in the Queue
-            mTimeLastRecoveryStartMs = SystemClock.elapsedRealtime();
-            sendMessage(obtainMessage(DctConstants.EVENT_DO_RECOVERY));
-        }
-
-        public void doRecovery() {
-            if (isAnyDataConnected()) {
-                // Go through a series of recovery steps, each action transitions to the next action
-                @RecoveryAction final int recoveryAction = getRecoveryAction();
-                final int signalStrength = mPhone.getSignalStrength().getLevel();
-                TelephonyMetrics.getInstance().writeSignalStrengthEvent(
-                        mPhone.getPhoneId(), signalStrength);
-                TelephonyMetrics.getInstance().writeDataStallEvent(
-                        mPhone.getPhoneId(), recoveryAction);
-                mLastAction = recoveryAction;
-                mLastActionReported = false;
-                broadcastDataStallDetected(recoveryAction);
-
-                switch (recoveryAction) {
-                    case RECOVERY_ACTION_GET_DATA_CALL_LIST:
-                        EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_GET_DATA_CALL_LIST,
-                            mSentSinceLastRecv);
-                        if (DBG) log("doRecovery() get data call list");
-                        mDataServiceManager.requestDataCallList(obtainMessage());
-                        putRecoveryAction(RECOVERY_ACTION_CLEANUP);
-                        break;
-                    case RECOVERY_ACTION_CLEANUP:
-                        EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_CLEANUP,
-                            mSentSinceLastRecv);
-                        if (DBG) log("doRecovery() cleanup all connections");
-                        cleanUpConnection(mApnContexts.get(ApnSetting.getApnTypeString(
-                                ApnSetting.TYPE_DEFAULT)));
-                        cleanUpConnection(mApnContexts.get(ApnSetting.getApnTypeString(
-                                ApnSetting.TYPE_ENTERPRISE)));
-                        putRecoveryAction(RECOVERY_ACTION_REREGISTER);
-                        break;
-                    case RECOVERY_ACTION_REREGISTER:
-                        EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_REREGISTER,
-                            mSentSinceLastRecv);
-                        if (DBG) log("doRecovery() re-register");
-                        mPhone.getServiceStateTracker().reRegisterNetwork(null);
-                        putRecoveryAction(RECOVERY_ACTION_RADIO_RESTART);
-                        break;
-                    case RECOVERY_ACTION_RADIO_RESTART:
-                        EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_RADIO_RESTART,
-                            mSentSinceLastRecv);
-                        if (DBG) log("restarting radio");
-                        restartRadio();
-                        reset();
-                        break;
-                    default:
-                        throw new RuntimeException("doRecovery: Invalid recoveryAction="
-                            + recoveryAction);
-                }
-                mSentSinceLastRecv = 0;
-            }
-        }
-
-        public void processNetworkStatusChanged(boolean isValid) {
-            setNetworkValidationState(isValid);
-            if (isValid) {
-                mIsValidNetwork = true;
-                reset();
-            } else {
-                if (mIsValidNetwork || isRecoveryAlreadyStarted()) {
-                    mIsValidNetwork = false;
-                    // Check and trigger a recovery if network switched from good
-                    // to bad or recovery is already started before.
-                    if (checkRecovery()) {
-                        if (DBG) log("trigger data stall recovery");
-                        triggerRecovery();
-                    }
-                }
-            }
-        }
-
-        public boolean isRecoveryOnBadNetworkEnabled() {
-            return Settings.Global.getInt(mResolver,
-                    Settings.Global.DATA_STALL_RECOVERY_ON_BAD_NETWORK, 1) == 1;
-        }
-
-        public boolean isNoRxDataStallDetectionEnabled() {
-            return mDataStallNoRxEnabled && !isRecoveryOnBadNetworkEnabled();
-        }
-    }
-
-    private void updateDataStallInfo() {
-        long sent, received;
-
-        TxRxSum preTxRxSum = new TxRxSum(mDataStallTxRxSum);
-        mDataStallTxRxSum.updateTotalTxRxSum();
-
-        if (VDBG_STALL) {
-            log("updateDataStallInfo: mDataStallTxRxSum=" + mDataStallTxRxSum +
-                    " preTxRxSum=" + preTxRxSum);
-        }
-
-        sent = mDataStallTxRxSum.txPkts - preTxRxSum.txPkts;
-        received = mDataStallTxRxSum.rxPkts - preTxRxSum.rxPkts;
-
-        if (RADIO_TESTS) {
-            if (SystemProperties.getBoolean("radio.test.data.stall", false)) {
-                log("updateDataStallInfo: radio.test.data.stall true received = 0;");
-                received = 0;
-            }
-        }
-        if ( sent > 0 && received > 0 ) {
-            if (VDBG_STALL) log("updateDataStallInfo: IN/OUT");
-            mSentSinceLastRecv = 0;
-            mDsRecoveryHandler.reset();
-        } else if (sent > 0 && received == 0) {
-            if (isPhoneStateIdle()) {
-                mSentSinceLastRecv += sent;
-            } else {
-                mSentSinceLastRecv = 0;
-            }
-            if (DBG) {
-                log("updateDataStallInfo: OUT sent=" + sent +
-                        " mSentSinceLastRecv=" + mSentSinceLastRecv);
-            }
-        } else if (sent == 0 && received > 0) {
-            if (VDBG_STALL) log("updateDataStallInfo: IN");
-            mSentSinceLastRecv = 0;
-            mDsRecoveryHandler.reset();
-        } else {
-            if (VDBG_STALL) log("updateDataStallInfo: NONE");
-        }
-    }
-
-    private boolean isPhoneStateIdle() {
-        for (int i = 0; i < mTelephonyManager.getPhoneCount(); i++) {
-            Phone phone = PhoneFactory.getPhone(i);
-            if (phone != null && phone.getState() != PhoneConstants.State.IDLE) {
-                log("isPhoneStateIdle false: Voice call active on phone " + i);
-                return false;
-            }
-        }
-        return true;
-    }
-
-    private void onDataStallAlarm(int tag) {
-        if (mDataStallAlarmTag != tag) {
-            if (DBG) {
-                log("onDataStallAlarm: ignore, tag=" + tag + " expecting " + mDataStallAlarmTag);
-            }
-            return;
-        }
-
-        if (DBG) log("Data stall alarm");
-        updateDataStallInfo();
-
-        int hangWatchdogTrigger = Settings.Global.getInt(mResolver,
-                Settings.Global.PDP_WATCHDOG_TRIGGER_PACKET_COUNT,
-                NUMBER_SENT_PACKETS_OF_HANG);
-
-        boolean suspectedStall = DATA_STALL_NOT_SUSPECTED;
-        if (mSentSinceLastRecv >= hangWatchdogTrigger) {
-            if (DBG) {
-                log("onDataStallAlarm: tag=" + tag + " do recovery action="
-                        + mDsRecoveryHandler.getRecoveryAction());
-            }
-            suspectedStall = DATA_STALL_SUSPECTED;
-            sendMessage(obtainMessage(DctConstants.EVENT_DO_RECOVERY));
-        } else {
-            if (VDBG_STALL) {
-                log("onDataStallAlarm: tag=" + tag + " Sent " + String.valueOf(mSentSinceLastRecv) +
-                    " pkts since last received, < watchdogTrigger=" + hangWatchdogTrigger);
-            }
-        }
-        startDataStallAlarm(suspectedStall);
-    }
-
-    protected void startDataStallAlarm(boolean suspectedStall) {
-        int delayInMs;
-
-        if (mDsRecoveryHandler.isNoRxDataStallDetectionEnabled() && isAnyDataConnected()) {
-            // If screen is on or data stall is currently suspected, set the alarm
-            // with an aggressive timeout.
-            if (mIsScreenOn || suspectedStall || mDsRecoveryHandler.isAggressiveRecovery()) {
-                delayInMs = Settings.Global.getInt(mResolver,
-                        Settings.Global.DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS,
-                        DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS_DEFAULT);
-            } else {
-                delayInMs = Settings.Global.getInt(mResolver,
-                        Settings.Global.DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS,
-                        DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS_DEFAULT);
-            }
-
-            mDataStallAlarmTag += 1;
-            if (VDBG_STALL) {
-                log("startDataStallAlarm: tag=" + mDataStallAlarmTag +
-                        " delay=" + (delayInMs / 1000) + "s");
-            }
-            Intent intent = new Intent(INTENT_DATA_STALL_ALARM);
-            intent.putExtra(INTENT_DATA_STALL_ALARM_EXTRA_TAG, mDataStallAlarmTag);
-            intent.putExtra(INTENT_DATA_STALL_ALARM_EXTRA_TRANSPORT_TYPE, mTransportType);
-            SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
-            mDataStallAlarmIntent = PendingIntent.getBroadcast(mPhone.getContext(), 0, intent,
-                    PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
-            mAlarmManager.set(AlarmManager.ELAPSED_REALTIME,
-                    SystemClock.elapsedRealtime() + delayInMs, mDataStallAlarmIntent);
-        } else {
-            if (VDBG_STALL) {
-                log("startDataStallAlarm: NOT started, no connection tag=" + mDataStallAlarmTag);
-            }
-        }
-    }
-
-    private void stopDataStallAlarm() {
-        if (VDBG_STALL) {
-            log("stopDataStallAlarm: current tag=" + mDataStallAlarmTag +
-                    " mDataStallAlarmIntent=" + mDataStallAlarmIntent);
-        }
-        mDataStallAlarmTag += 1;
-        if (mDataStallAlarmIntent != null) {
-            mAlarmManager.cancel(mDataStallAlarmIntent);
-            mDataStallAlarmIntent = null;
-        }
-    }
-
-    private void restartDataStallAlarm() {
-        if (!isAnyDataConnected()) return;
-        // To be called on screen status change.
-        // Do not cancel the alarm if it is set with aggressive timeout.
-        if (mDsRecoveryHandler.isAggressiveRecovery()) {
-            if (DBG) log("restartDataStallAlarm: action is pending. not resetting the alarm.");
-            return;
-        }
-        if (VDBG_STALL) log("restartDataStallAlarm: stop then start.");
-        stopDataStallAlarm();
-        startDataStallAlarm(DATA_STALL_NOT_SUSPECTED);
-    }
-
-    /**
-     * Provisioning APN
-     */
-    private void onActionIntentProvisioningApnAlarm(Intent intent) {
-        if (DBG) log("onActionIntentProvisioningApnAlarm: action=" + intent.getAction());
-        Message msg = obtainMessage(DctConstants.EVENT_PROVISIONING_APN_ALARM,
-                intent.getAction());
-        msg.arg1 = intent.getIntExtra(PROVISIONING_APN_ALARM_TAG_EXTRA, 0);
-        sendMessage(msg);
-    }
-
-    private void startProvisioningApnAlarm() {
-        int delayInMs = Settings.Global.getInt(mResolver,
-                                Settings.Global.PROVISIONING_APN_ALARM_DELAY_IN_MS,
-                                PROVISIONING_APN_ALARM_DELAY_IN_MS_DEFAULT);
-        if (TelephonyUtils.IS_DEBUGGABLE) {
-            // Allow debug code to use a system property to provide another value
-            String delayInMsStrg = Integer.toString(delayInMs);
-            delayInMsStrg = System.getProperty(DEBUG_PROV_APN_ALARM, delayInMsStrg);
-            try {
-                delayInMs = Integer.parseInt(delayInMsStrg);
-            } catch (NumberFormatException e) {
-                loge("startProvisioningApnAlarm: e=" + e);
-            }
-        }
-        mProvisioningApnAlarmTag += 1;
-        if (DBG) {
-            log("startProvisioningApnAlarm: tag=" + mProvisioningApnAlarmTag +
-                    " delay=" + (delayInMs / 1000) + "s");
-        }
-        Intent intent = new Intent(INTENT_PROVISIONING_APN_ALARM);
-        intent.putExtra(PROVISIONING_APN_ALARM_TAG_EXTRA, mProvisioningApnAlarmTag);
-        mProvisioningApnAlarmIntent = PendingIntent.getBroadcast(mPhone.getContext(), 0, intent,
-                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
-        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                SystemClock.elapsedRealtime() + delayInMs, mProvisioningApnAlarmIntent);
-    }
-
-    private void stopProvisioningApnAlarm() {
-        if (DBG) {
-            log("stopProvisioningApnAlarm: current tag=" + mProvisioningApnAlarmTag +
-                    " mProvsioningApnAlarmIntent=" + mProvisioningApnAlarmIntent);
-        }
-        mProvisioningApnAlarmTag += 1;
-        if (mProvisioningApnAlarmIntent != null) {
-            mAlarmManager.cancel(mProvisioningApnAlarmIntent);
-            mProvisioningApnAlarmIntent = null;
-        }
-    }
-
-    /**
-     * 5G connection reevaluation alarm
-     */
-    private void startWatchdogAlarm() {
-        sendMessageDelayed(obtainMessage(DctConstants.EVENT_NR_TIMER_WATCHDOG), mWatchdogTimeMs);
-        mWatchdog = true;
-    }
-
-    private void stopWatchdogAlarm() {
-        removeMessages(DctConstants.EVENT_NR_TIMER_WATCHDOG);
-        mWatchdog = false;
-    }
-
-    private void onDataServiceBindingChanged(boolean bound) {
-        if (!bound) {
-            if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
-                boolean connPersistenceOnRestart = mPhone.getContext().getResources()
-                   .getBoolean(com.android
-                       .internal.R.bool.config_wlan_data_service_conn_persistence_on_restart);
-                if (!connPersistenceOnRestart) {
-                    cleanUpAllConnectionsInternal(false, Phone.REASON_IWLAN_DATA_SERVICE_DIED);
-                }
-            }
-        } else {
-            //reset throttling after binding to data service
-            mDataThrottler.reset();
-        }
-        mDataServiceBound = bound;
-    }
-
-    public static String requestTypeToString(@RequestNetworkType int type) {
-        switch (type) {
-            case REQUEST_TYPE_NORMAL: return "NORMAL";
-            case REQUEST_TYPE_HANDOVER: return "HANDOVER";
-        }
-        return "UNKNOWN";
-    }
-
-    public static String releaseTypeToString(@ReleaseNetworkType int type) {
-        switch (type) {
-            case RELEASE_TYPE_NORMAL: return "NORMAL";
-            case RELEASE_TYPE_DETACH: return "DETACH";
-            case RELEASE_TYPE_HANDOVER: return "HANDOVER";
-        }
-        return "UNKNOWN";
-    }
-
-    @RilRadioTechnology
-    protected int getDataRat() {
-        ServiceState ss = mPhone.getServiceState();
-        NetworkRegistrationInfo nrs = ss.getNetworkRegistrationInfo(
-                NetworkRegistrationInfo.DOMAIN_PS, mTransportType);
-        if (nrs != null) {
-            return ServiceState.networkTypeToRilRadioTechnology(nrs.getAccessNetworkTechnology());
-        }
-        return ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
-    }
-
-    @RilRadioTechnology
-    private int getVoiceRat() {
-        ServiceState ss = mPhone.getServiceState();
-        NetworkRegistrationInfo nrs = ss.getNetworkRegistrationInfo(
-                NetworkRegistrationInfo.DOMAIN_CS, mTransportType);
-        if (nrs != null) {
-            return ServiceState.networkTypeToRilRadioTechnology(nrs.getAccessNetworkTechnology());
-        }
-        return ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
-    }
-
-    private void read5GConfiguration() {
-        if (DBG) log("read5GConfiguration");
-        String[] bandwidths = CarrierConfigManager.getDefaultConfig().getStringArray(
-                CarrierConfigManager.KEY_BANDWIDTH_STRING_ARRAY);
-        boolean useLte = false;
-        CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
-                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        if (configManager != null) {
-            PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId());
-            if (b != null) {
-                if (b.getStringArray(CarrierConfigManager.KEY_BANDWIDTH_STRING_ARRAY) != null) {
-                    bandwidths = b.getStringArray(CarrierConfigManager.KEY_BANDWIDTH_STRING_ARRAY);
-                }
-                useLte = b.getBoolean(CarrierConfigManager
-                        .KEY_BANDWIDTH_NR_NSA_USE_LTE_VALUE_FOR_UPLINK_BOOL);
-                mWatchdogTimeMs = b.getLong(CarrierConfigManager.KEY_5G_WATCHDOG_TIME_MS_LONG);
-                mNrNsaAllUnmetered = b.getBoolean(CarrierConfigManager.KEY_UNMETERED_NR_NSA_BOOL);
-                mNrNsaMmwaveUnmetered = b.getBoolean(
-                        CarrierConfigManager.KEY_UNMETERED_NR_NSA_MMWAVE_BOOL);
-                mNrNsaSub6Unmetered = b.getBoolean(
-                        CarrierConfigManager.KEY_UNMETERED_NR_NSA_SUB6_BOOL);
-                mNrSaAllUnmetered = b.getBoolean(CarrierConfigManager.KEY_UNMETERED_NR_SA_BOOL);
-                mNrSaMmwaveUnmetered = b.getBoolean(
-                        CarrierConfigManager.KEY_UNMETERED_NR_SA_MMWAVE_BOOL);
-                mNrSaSub6Unmetered = b.getBoolean(
-                        CarrierConfigManager.KEY_UNMETERED_NR_SA_SUB6_BOOL);
-                mNrNsaRoamingUnmetered = b.getBoolean(
-                        CarrierConfigManager.KEY_UNMETERED_NR_NSA_WHEN_ROAMING_BOOL);
-                mLteEndcUsingUserDataForRrcDetection = b.getBoolean(
-                        CarrierConfigManager.KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL);
-            }
-        }
-        updateLinkBandwidths(bandwidths, useLte);
-    }
-
-    public boolean getLteEndcUsingUserDataForIdleDetection() {
-        return mLteEndcUsingUserDataForRrcDetection;
-    }
-
-    /**
-     * Register for physical link status (i.e. RRC state) changed event.
-     * if {@link CarrierConfigManager.KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL} is true,
-     * then physical link state is focusing on "internet data connection" instead of RRC state.
-     *
-     * @param h The handler
-     * @param what The event
-     */
-    public void registerForPhysicalLinkStatusChanged(Handler h, int what) {
-        mDcc.registerForPhysicalLinkStatusChanged(h, what);
-    }
-
-    /**
-     * Unregister from physical link status (i.e. RRC state) changed event.
-     *
-     * @param h The previously registered handler
-     */
-    public void unregisterForPhysicalLinkStatusChanged(Handler h) {
-        mDcc.unregisterForPhysicalLinkStatusChanged(h);
-    }
-
-    // We use a specialized equals function in Apn setting when checking if an active
-    // data connection is still legitimate to use against a different apn setting.
-    // This method is extracted to a function to ensure that any future changes to this check will
-    // be applied to both cleanUpConnectionsOnUpdatedApns and checkForCompatibleDataConnection.
-    // Fix for b/158908392.
-    private boolean areCompatible(ApnSetting apnSetting1, ApnSetting apnSetting2) {
-        return apnSetting1.equals(apnSetting2,
-                mPhone.getServiceState().getDataRoamingFromRegistration());
-    }
-
-    @NonNull
-    private PersistableBundle getCarrierConfig() {
-        CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
-                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        if (configManager != null) {
-            // If an invalid subId is used, this bundle will contain default values.
-            PersistableBundle config = configManager.getConfigForSubId(mPhone.getSubId());
-            if (config != null) {
-                return config;
-            }
-        }
-        // Return static default defined in CarrierConfigManager.
-        return CarrierConfigManager.getDefaultConfig();
-    }
-
-    /**
-     * @return The data service manager.
-     */
-    public @NonNull DataServiceManager getDataServiceManager() {
-        return mDataServiceManager;
-    }
-
-    /**
-     * @return The data throttler
-     */
-    public @NonNull DataThrottler getDataThrottler() {
-        return mDataThrottler;
-    }
-
-    private void showProvisioningNotification() {
-        final Intent intent = new Intent(DcTracker.INTENT_PROVISION);
-        intent.putExtra(DcTracker.EXTRA_PROVISION_PHONE_ID, mPhone.getPhoneId());
-        final PendingIntent pendingIntent = PendingIntent.getBroadcast(
-                mPhone.getContext(), 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE);
-
-        final Resources r = mPhone.getContext().getResources();
-        final String title = r.getString(R.string.network_available_sign_in, 0);
-        final String details = mTelephonyManager.getNetworkOperator(mPhone.getSubId());
-        final Notification.Builder builder = new Notification.Builder(mPhone.getContext())
-                .setWhen(System.currentTimeMillis())
-                .setSmallIcon(R.drawable.stat_notify_rssi_in_range)
-                .setChannelId(NotificationChannelController.CHANNEL_ID_MOBILE_DATA_STATUS)
-                .setAutoCancel(true)
-                .setTicker(title)
-                .setColor(mPhone.getContext().getColor(
-                        com.android.internal.R.color.system_notification_accent_color))
-                .setContentTitle(title)
-                .setContentText(details)
-                .setContentIntent(pendingIntent)
-                .setLocalOnly(true)
-                .setOnlyAlertOnce(true);
-
-        final Notification notification = builder.build();
-        try {
-            getNotificationManager().notify(NOTIFICATION_TAG, mPhone.getPhoneId(), notification);
-        } catch (final NullPointerException npe) {
-            Log.e(mLogTag, "showProvisioningNotification: error showing notification", npe);
-        }
-    }
-
-    private void hideProvisioningNotification() {
-        try {
-            getNotificationManager().cancel(NOTIFICATION_TAG, mPhone.getPhoneId());
-        } catch (final NullPointerException npe) {
-            Log.e(mLogTag, "hideProvisioningNotification: error hiding notification", npe);
-        }
-    }
-
-    private NotificationManager getNotificationManager() {
-        return (NotificationManager) mPhone.getContext()
-                .createContextAsUser(UserHandle.ALL, 0 /* flags */)
-                .getSystemService(Context.NOTIFICATION_SERVICE);
-    }
-}
diff --git a/src/java/com/android/internal/telephony/dataconnection/README.txt b/src/java/com/android/internal/telephony/dataconnection/README.txt
deleted file mode 100644
index e613a00..0000000
--- a/src/java/com/android/internal/telephony/dataconnection/README.txt
+++ /dev/null
@@ -1,71 +0,0 @@
-This package contains classes used to manage a DataConnection.
-
-A criticial aspect of this class is that most objects in this
-package run on the same thread except DataConnectionTracker
-This makes processing efficient as it minimizes context
-switching and it eliminates issues with multi-threading.
-
-This can be done because all actions are either asynchronous
-or are known to be non-blocking and fast. At this time only
-DcTesterDeactivateAll takes specific advantage of this
-single threading knowledge by using Dcc#mDcListAll so be
-very careful when making changes that break this assumption.
-
-A related change was in DataConnectionAc I added code that
-checks to see if the caller is on a different thread. If
-it is then the AsyncChannel#sendMessageSynchronously is
-used. If the caller is on the same thread then a getter
-is used. This allows the DCAC to be used from any thread
-and was required to fix a bug when Dcc called
-PhoneBase#notifyDataConnection which calls DCT#getLinkProperties
-and DCT#getLinkCapabilities which call Dcc all on the same
-thread. Without this change there was a dead lock when
-sendMessageSynchronously blocks.
-
-
-== Testing ==
-
-The following are Intents that can be sent for testing pruproses on
-DEBUGGABLE builds (userdebug, eng)
-
-*) Causes bringUp and retry requests to fail for all DC's
-
-  adb shell am broadcast -a com.android.internal.telephony.dataconnection.action_fail_bringup --ei counter 2 --ei fail_cause -3
-
-*) Causes all DC's to get torn down, simulating a temporary network outage:
-
-  adb shell am broadcast -a com.android.internal.telephony.dataconnection.action_deactivate_all
-
-*) To simplify testing we also have detach and attach simulations below where {x} is gsm, cdma or sip
-
-  adb shell am broadcast -a com.android.internal.telephony.{x}.action_detached
-  adb shell am broadcast -a com.android.internal.telephony.{x}.action_attached
-
-
-== System properties for Testing ==
-
-On debuggable builds (userdebug, eng) you can change additional
-settings through system properties.  These properties can be set with
-"setprop" for the current boot, or added to local.prop to persist
-across boots.
-
-device# setprop key value
-
-device# echo "key=value" >> /data/local.prop
-device# chmod 644 /data/local.prop
-
-
--- Retry configuration --
-
-You can replace the connection retry configuration.  For example, you
-could change it to perform 4 retries at 5 second intervals:
-
-device# setprop test.data_retry_config "5000,5000,5000"
-
-
--- Roaming --
-
-You can force the telephony stack to always assume that it's roaming
-to verify higher-level framework functionality:
-
-device# setprop telephony.test.forceRoaming true
diff --git a/src/java/com/android/internal/telephony/dataconnection/TransportManager.java b/src/java/com/android/internal/telephony/dataconnection/TransportManager.java
deleted file mode 100644
index 63358f4..0000000
--- a/src/java/com/android/internal/telephony/dataconnection/TransportManager.java
+++ /dev/null
@@ -1,279 +0,0 @@
-/*
- * Copyright 2018 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.dataconnection;
-
-import android.annotation.Nullable;
-import android.os.Handler;
-import android.os.Message;
-import android.os.RegistrantList;
-import android.telephony.AccessNetworkConstants;
-import android.telephony.Annotation.ApnType;
-import android.telephony.CarrierConfigManager;
-import android.telephony.data.ApnSetting;
-import android.util.LocalLog;
-import android.util.SparseIntArray;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.data.AccessNetworksManager;
-import com.android.internal.telephony.data.TelephonyNetworkFactory;
-import com.android.telephony.Rlog;
-
-import java.util.concurrent.TimeUnit;
-
-/**
- * This class represents the transport manager which manages available transports (i.e. WWAN or
- * WLAN) and determine the correct transport for {@link TelephonyNetworkFactory} to handle the data
- * requests.
- *
- * The device can operate in the following modes, which is stored in the system properties
- * ro.telephony.iwlan_operation_mode. If the system properties is missing, then it's tied to
- * IRadio version. For 1.4 or above, it's AP-assisted mdoe. For 1.3 or below, it's legacy mode.
- *
- * Legacy mode:
- *      Frameworks send all data requests to the default data service, which is the cellular data
- *      service. IWLAN should be still reported as a RAT on cellular network service.
- *
- * AP-assisted mode:
- *      IWLAN is handled by IWLAN data service extending {@link android.telephony.data.DataService},
- *      IWLAN network service extending {@link android.telephony.NetworkService}, and qualified
- *      network service extending {@link android.telephony.data.QualifiedNetworksService}.
- *
- *      The following settings for service package name need to be configured properly for
- *      frameworks to bind.
- *
- *      Package name of data service:
- *          The resource overlay 'config_wlan_data_service_package' or,
- *          the carrier config
- *          {@link CarrierConfigManager#KEY_CARRIER_DATA_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING}.
- *          The carrier config takes precedence over the resource overlay if both exist.
- *
- *      Package name of network service
- *          The resource overlay 'config_wlan_network_service_package' or
- *          the carrier config
- *          {@link CarrierConfigManager#KEY_CARRIER_NETWORK_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING}.
- *          The carrier config takes precedence over the resource overlay if both exist.
- *
- *      Package name of qualified network service
- *          The resource overlay 'config_qualified_networks_service_package' or
- *          the carrier config
- *          {@link CarrierConfigManager#
- *          KEY_CARRIER_QUALIFIED_NETWORKS_SERVICE_PACKAGE_OVERRIDE_STRING}.
- *          The carrier config takes precedence over the resource overlay if both exist.
- */
-public class TransportManager extends Handler {
-    private final String mLogTag;
-
-    private static final int EVENT_QUALIFIED_NETWORKS_CHANGED = 1;
-
-    private static final int EVENT_EVALUATE_TRANSPORT_PREFERENCE = 2;
-
-    // Delay the re-evaluation if transport fall back. QNS will need to quickly change the
-    // preference back to the original transport to avoid another handover request.
-    private static final long FALL_BACK_REEVALUATE_DELAY_MILLIS = TimeUnit.SECONDS.toMillis(3);
-
-    private final Phone mPhone;
-
-    private final LocalLog mLocalLog = new LocalLog(64);
-
-    @Nullable
-    private AccessNetworksManager mAccessNetworksManager;
-
-    /**
-     * The pending handover list. This is a list of APNs that are being handover to the new
-     * transport. The entry will be removed once handover is completed. The key
-     * is the APN type, and the value is the target transport that the APN is handovered to.
-     */
-    private final SparseIntArray mPendingHandoverApns;
-
-    /**
-     * The registrants for listening data handover needed events.
-     */
-    private final RegistrantList mHandoverNeededEventRegistrants;
-
-    /**
-     * Handover parameters
-     */
-    @VisibleForTesting
-    public static final class HandoverParams {
-        /**
-         * The callback for handover complete.
-         */
-        public interface HandoverCallback {
-            /**
-             * Called when handover is completed.
-             *
-             * @param success {@true} if handover succeeded, otherwise failed.
-             * @param fallback {@true} if handover failed, the data connection fallback to the
-             * original transport
-             */
-            void onCompleted(boolean success, boolean fallback);
-        }
-
-        public final @ApnType int apnType;
-        public final int targetTransport;
-        public final HandoverCallback callback;
-
-        @VisibleForTesting
-        public HandoverParams(int apnType, int targetTransport, HandoverCallback callback) {
-            this.apnType = apnType;
-            this.targetTransport = targetTransport;
-            this.callback = callback;
-        }
-    }
-
-    public TransportManager(Phone phone) {
-        mPhone = phone;
-        mPendingHandoverApns = new SparseIntArray();
-        mHandoverNeededEventRegistrants = new RegistrantList();
-        mLogTag = TransportManager.class.getSimpleName() + "-" + mPhone.getPhoneId();
-        mAccessNetworksManager = mPhone.getAccessNetworksManager();
-        mAccessNetworksManager.registerForQualifiedNetworksChanged(this,
-                EVENT_QUALIFIED_NETWORKS_CHANGED);
-    }
-
-    @Override
-    public void handleMessage(Message msg) {
-        switch (msg.what) {
-            case EVENT_QUALIFIED_NETWORKS_CHANGED:
-                if (!hasMessages(EVENT_EVALUATE_TRANSPORT_PREFERENCE)) {
-                    sendEmptyMessage(EVENT_EVALUATE_TRANSPORT_PREFERENCE);
-                }
-                break;
-            case EVENT_EVALUATE_TRANSPORT_PREFERENCE:
-                evaluateTransportPreference();
-                break;
-            default:
-                loge("Unexpected event " + msg.what);
-                break;
-        }
-    }
-
-    /**
-     * Set the current transport of apn type.
-     *
-     * @param apnType The APN type
-     * @param transport The transport. Must be WWAN or WLAN.
-     */
-    private synchronized void setCurrentTransport(@ApnType int apnType, int transport) {
-        mAccessNetworksManager.setCurrentTransport(apnType, transport);
-    }
-
-    private boolean isHandoverPending() {
-        return mPendingHandoverApns.size() > 0;
-    }
-
-    /**
-     * Evaluate the preferred transport for each APN type to see if handover is needed.
-     */
-    private void evaluateTransportPreference() {
-        // Simultaneously handover is not supported today. Preference will be re-evaluated after
-        // handover completed.
-        if (isHandoverPending()) return;
-        logl("evaluateTransportPreference");
-        for (int apnType : AccessNetworksManager.SUPPORTED_APN_TYPES) {
-            int targetTransport = mAccessNetworksManager.getPreferredTransport(apnType);
-            if (targetTransport != mAccessNetworksManager.getCurrentTransport(apnType)) {
-                logl("Handover started for APN type: "
-                        + ApnSetting.getApnTypeString(apnType)
-                        + ", target transport: "
-                        + AccessNetworkConstants.transportTypeToString(targetTransport));
-                mPendingHandoverApns.put(apnType, targetTransport);
-                mHandoverNeededEventRegistrants.notifyResult(
-                        new HandoverParams(apnType, targetTransport,
-                                (success, fallback) -> {
-                                    // The callback for handover completed.
-                                    if (success) {
-                                        logl("Handover succeeded for APN type "
-                                                + ApnSetting.getApnTypeString(apnType));
-                                    } else {
-                                        logl("APN type "
-                                                + ApnSetting.getApnTypeString(apnType)
-                                                + " handover to "
-                                                + AccessNetworkConstants.transportTypeToString(
-                                                targetTransport) + " failed"
-                                                + ", fallback=" + fallback);
-                                    }
-
-                                    long delay = 0;
-                                    if (fallback) {
-                                        // No need to change the preference because we should
-                                        // fallback. Re-evaluate after few seconds to give QNS
-                                        // some time to change the preference back to the original
-                                        // transport.
-                                        delay = FALL_BACK_REEVALUATE_DELAY_MILLIS;
-                                    } else {
-                                        // If handover succeeds or failed without falling back
-                                        // to the original transport, we should move to the new
-                                        // transport (even if it is failed).
-                                        setCurrentTransport(apnType, targetTransport);
-                                    }
-                                    mPendingHandoverApns.delete(apnType);
-                                    sendEmptyMessageDelayed(EVENT_EVALUATE_TRANSPORT_PREFERENCE,
-                                            delay);
-                                }));
-
-                // Return here instead of processing the next APN type. The next APN type for
-                // handover will be evaluate again once current handover is completed.
-                return;
-            }
-        }
-    }
-
-    /**
-     * Register for data handover needed event
-     *
-     * @param h The handler of the event
-     * @param what The id of the event
-     */
-    public void registerForHandoverNeededEvent(Handler h, int what) {
-        if (h != null) {
-            mHandoverNeededEventRegistrants.addUnique(h, what, null);
-        }
-    }
-
-    /**
-     * Unregister for data handover needed event
-     *
-     * @param h The handler
-     */
-    public void unregisterForHandoverNeededEvent(Handler h) {
-        mHandoverNeededEventRegistrants.remove(h);
-    }
-
-    /**
-     * Registers the data throttler with DcTracker.
-     */
-    public void registerDataThrottler(DataThrottler dataThrottler) {
-        if (mAccessNetworksManager != null) {
-            mAccessNetworksManager.registerDataThrottler(dataThrottler);
-        }
-    }
-
-    private void logl(String s) {
-        log(s);
-        mLocalLog.log(s);
-    }
-
-    private void log(String s) {
-        Rlog.d(mLogTag, s);
-    }
-
-    private void loge(String s) {
-        Rlog.e(mLogTag, s);
-    }
-}
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 276d82a..9b44001 100644
--- a/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
+++ b/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
@@ -20,14 +20,15 @@
 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;
@@ -36,18 +37,21 @@
 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;
 import com.android.phone.ecc.nano.ProtobufEccData.EccInfo;
@@ -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
@@ -141,10 +157,6 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             if (intent.getAction().equals(
-                    CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
-                onCarrierConfigChanged();
-                return;
-            } else if (intent.getAction().equals(
                     TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED)) {
                 int phoneId = intent.getIntExtra(PhoneConstants.PHONE_KEY, -1);
                 if (phoneId == mPhone.getPhoneId()) {
@@ -164,27 +176,35 @@
     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) {
-                PersistableBundle b = configMgr.getConfigForSubId(mPhone.getSubId());
-                if (b != null) {
+                PersistableBundle b = CarrierConfigManager.getCarrierConfigSubset(
+                        mPhone.getContext(),
+                        mPhone.getSubId(),
+                        CarrierConfigManager.KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY);
+                if (!b.isEmpty()) {
                     mEmergencyNumberPrefix = b.getStringArray(
                             CarrierConfigManager.KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY);
                 }
+
+                // Callback which directly handle config change should be executed on handler thread
+                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.");
             }
-
-            // Receive Carrier Config Changes
-            IntentFilter filter = new IntentFilter(
-                    CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
-            // Receive Telephony Network Country Changes
-            filter.addAction(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED);
-
-            mPhone.getContext().registerReceiver(mIntentReceiver, filter);
         } else {
             loge("mPhone is null.");
         }
@@ -269,7 +289,7 @@
     @VisibleForTesting
     public boolean isSimAbsent() {
         for (Phone phone: PhoneFactory.getPhones()) {
-            int 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
@@ -284,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)
@@ -327,23 +347,26 @@
         }
     }
 
-    private void onCarrierConfigChanged() {
+    private void onCarrierConfigUpdated(int slotIndex) {
         if (mPhone != null) {
-            CarrierConfigManager configMgr = (CarrierConfigManager)
-                    mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
-            if (configMgr != null) {
-                PersistableBundle b = configMgr.getConfigForSubId(mPhone.getSubId());
-                if (b != null) {
-                    String[] emergencyNumberPrefix = b.getStringArray(
+            if (slotIndex != mPhone.getPhoneId()) return;
+
+            PersistableBundle b =
+                    CarrierConfigManager.getCarrierConfigSubset(
+                            mPhone.getContext(),
+                            mPhone.getSubId(),
                             CarrierConfigManager.KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY);
-                    if (!Arrays.equals(mEmergencyNumberPrefix, emergencyNumberPrefix)) {
-                        this.obtainMessage(EVENT_UPDATE_EMERGENCY_NUMBER_PREFIX,
-                                emergencyNumberPrefix).sendToTarget();
-                    }
+            if (!b.isEmpty()) {
+                String[] emergencyNumberPrefix =
+                        b.getStringArray(
+                                CarrierConfigManager.KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY);
+                if (!Arrays.equals(mEmergencyNumberPrefix, emergencyNumberPrefix)) {
+                    this.obtainMessage(EVENT_UPDATE_EMERGENCY_NUMBER_PREFIX, emergencyNumberPrefix)
+                            .sendToTarget();
                 }
             }
         } else {
-            loge("onCarrierConfigChanged mPhone is null.");
+            loge("onCarrierConfigurationChanged mPhone is null.");
         }
     }
 
@@ -404,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.");
@@ -445,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<>();
@@ -463,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));
                     }
                 }
             }
@@ -479,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<>();
@@ -525,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));
                     }
                 }
             }
@@ -543,6 +632,8 @@
                 && mCurrentDatabaseVersion < otaDatabaseVersion) {
             mCurrentDatabaseVersion = otaDatabaseVersion;
             mEmergencyNumberListFromDatabase = updatedOtaEmergencyNumberList;
+            mNormalRoutedNumbers.clear();
+            mNormalRoutedNumbers = otaNormalRoutedNumbers;
         }
         return otaDatabaseVersion;
     }
@@ -591,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);
@@ -622,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(
@@ -687,7 +779,11 @@
         }
         mergedEmergencyNumberList.addAll(mEmergencyNumberListWithPrefix);
         mergedEmergencyNumberList.addAll(mEmergencyNumberListFromTestMode);
-        EmergencyNumber.mergeSameNumbersInEmergencyNumberList(mergedEmergencyNumberList);
+        if (shouldDeterminingOfUrnsAndCategoriesWhileMergingIgnored()) {
+            EmergencyNumber.mergeSameNumbersInEmergencyNumberList(mergedEmergencyNumberList);
+        } else {
+            EmergencyNumber.mergeSameNumbersInEmergencyNumberList(mergedEmergencyNumberList, true);
+        }
         mEmergencyNumberList = mergedEmergencyNumberList;
     }
 
@@ -698,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;
     }
 
     /**
@@ -710,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;
         }
@@ -726,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
@@ -840,6 +998,10 @@
         return mCurrentDatabaseVersion;
     }
 
+    public int getEmergencyNumberOtaDbVersion() {
+        return mCurrentOtaDatabaseVersion;
+    }
+
     private synchronized void updateEmergencyCountryIso(String countryIso) {
         mCountryIso = countryIso;
         if (!TextUtils.isEmpty(mCountryIso)) {
@@ -855,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));
         }
@@ -886,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()));
                     }
                 }
             }
@@ -913,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);
@@ -936,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, "", "",
@@ -952,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;
 
@@ -976,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.");
@@ -1038,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;
             }
         }
 
@@ -1107,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) {
@@ -1134,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);
@@ -1142,7 +1213,12 @@
                     mEmergencyNumberListFromDatabase));
         }
         mergedEmergencyNumberList.addAll(getEmergencyNumberListTestMode());
-        EmergencyNumber.mergeSameNumbersInEmergencyNumberList(mergedEmergencyNumberList);
+
+        if (shouldDeterminingOfUrnsAndCategoriesWhileMergingIgnored()) {
+            EmergencyNumber.mergeSameNumbersInEmergencyNumberList(mergedEmergencyNumberList);
+        } else {
+            EmergencyNumber.mergeSameNumbersInEmergencyNumberList(mergedEmergencyNumberList, true);
+        }
         return mergedEmergencyNumberList;
     }
 
@@ -1158,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(
@@ -1182,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
@@ -1190,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(" ========================================= ");
 
@@ -1229,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 f337141..2f73c91 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccCardController.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccCardController.java
@@ -39,7 +39,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-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;
 import com.android.internal.telephony.uicc.UiccPort;
@@ -51,6 +51,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.List;
 
 /** Backing implementation of {@link EuiccCardManager}. */
 public class EuiccCardController extends IEuiccCardController.Stub {
@@ -413,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);
             }
@@ -649,9 +650,8 @@
             @Override
             public void onResult(Void result) {
                 Log.i(TAG, "Request subscription info list refresh after delete.");
-                SubscriptionController.getInstance()
-                        .requestEmbeddedSubscriptionInfoListRefresh(
-                                mUiccController.convertToPublicCardId(cardId));
+                SubscriptionManagerService.getInstance().updateEmbeddedSubscriptions(
+                        List.of(mUiccController.convertToPublicCardId(cardId)), null);
                 try {
                     callback.onComplete(EuiccCardManager.RESULT_OK);
                 } catch (RemoteException exception) {
@@ -701,9 +701,8 @@
             @Override
             public void onResult(Void result) {
                 Log.i(TAG, "Request subscription info list refresh after reset memory.");
-                SubscriptionController.getInstance()
-                        .requestEmbeddedSubscriptionInfoListRefresh(
-                                mUiccController.convertToPublicCardId(cardId));
+                SubscriptionManagerService.getInstance().updateEmbeddedSubscriptions(
+                        List.of(mUiccController.convertToPublicCardId(cardId)), null);
                 try {
                     callback.onComplete(EuiccCardManager.RESULT_OK);
                 } catch (RemoteException exception) {
@@ -1190,9 +1189,8 @@
             @Override
             public void onResult(byte[] result) {
                 Log.i(TAG, "Request subscription info list refresh after install.");
-                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 965dd22..a5b95c3 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccController.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccController.java
@@ -59,8 +59,8 @@
 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;
 import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.telephony.uicc.UiccPort;
@@ -385,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");
         }
@@ -392,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 {
@@ -601,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));
@@ -713,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));
@@ -862,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");
@@ -1147,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);
@@ -1175,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);
@@ -1585,9 +1591,9 @@
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     public void refreshSubscriptionsAndSendResult(
             PendingIntent callbackIntent, int resultCode, Intent extrasIntent) {
-        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. */
@@ -1840,8 +1846,11 @@
     // other SIM. The target SIM should be an eUICC.
     // For a single-active subscription phone, checks whether the caller can manage any active
     // embedded subscription.
-    // From Android T, If embedded slot supports Multiple Enabled Profiles then should check if
-    // the calling app has carrier privilege over the subscription on the target port index.
+    // From Android T, if usePortIndex is true then verify whether the calling app has carrier
+    // privilege over the active embedded subscription on the target port index.
+    // If usePortIndex is false then check whether the calling app can manage any active
+    // subscription on any of the active ports, if there are no active embedded subscriptions then
+    // verify whether the calling app can manage any active subscription on any of the other SIM.
     private boolean canManageSubscriptionOnTargetSim(int cardId, String callingPackage,
             boolean usePortIndex, int targetPortIndex) {
         List<SubscriptionInfo> subInfoList = mSubscriptionManager
@@ -1861,11 +1870,9 @@
                 return false;
             }
             boolean isEuicc = false;
-            boolean isMultipleEnabledProfilesSupported = false;
             for (UiccCardInfo info : cardInfos) {
                 if (info != null && info.getCardId() == cardId && info.isEuicc()) {
                     isEuicc = true;
-                    isMultipleEnabledProfilesSupported = info.isMultipleEnabledProfilesSupported();
                     break;
                 }
             }
@@ -1874,21 +1881,34 @@
                 return false;
             }
 
-            // If the caller can't manage the active embedded subscription on the target SIM, return
-            // false. If the caller can manage the active embedded subscription on the target SIM,
-            // return true directly.
-            for (SubscriptionInfo subInfo : subInfoList) {
-                // 1. subInfo.isEmbedded() can only be true for the target SIM.
-                // 2. Check whether the caller can manage subscription on the target portIndex
-                // (i.e. subInfo.getPortIndex() == targetPortIndex condition) only in case if
-                // isMultipleEnabledProfilesSupported and usePortIndex both are true.
-                if (subInfo.isEmbedded() && subInfo.getCardId() == cardId
-                        && (!isMultipleEnabledProfilesSupported || !usePortIndex
-                        || subInfo.getPortIndex() == targetPortIndex)) {
-                    return mSubscriptionManager.canManageSubscription(subInfo, callingPackage);
-                }
-            }
+            // If the caller can't manage the active embedded subscription on the target SIM port,
+            // return false. If the caller can manage the active embedded subscription on the
+            // target SIM port, return true directly.
+            boolean hasActiveEmbeddedSubscription = subInfoList.stream().anyMatch(
+                    subInfo -> subInfo.isEmbedded() && subInfo.getCardId() == cardId
+                            && (!usePortIndex || subInfo.getPortIndex() == targetPortIndex));
+            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
+                // embedded subscription on any of the active ports.
 
+                // 1. If usePortIndex is true, check whether the caller can manage subscription on
+                // the target port.
+                // 2. If usePortIndex is false, check whether the caller can manage subscription on
+                // any of the active ports.
+                for (SubscriptionInfo subInfo : subInfoList) {
+                    // subInfo.isEmbedded() can only be true for the target SIM.
+                    if (subInfo.isEmbedded()
+                            && subInfo.getCardId() == cardId
+                            && (!usePortIndex || subInfo.getPortIndex() == targetPortIndex)
+                            && mSubscriptionManager.canManageSubscription(
+                            subInfo, callingPackage)) {
+                        return true;
+                    }
+                }
+                Log.i(TAG, "canManageSubscriptionOnTargetSim cannot manage embedded subscription");
+                return false;
+            }
             // There is no active subscription on the target SIM, checks whether the caller can
             // manage any active subscription on any other SIM.
             final long token = Binder.clearCallingIdentity();
@@ -1923,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 dec2468..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;
@@ -61,6 +62,7 @@
 import com.android.internal.telephony.util.ArrayUtils;
 import com.android.telephony.Rlog;
 
+import java.util.ArrayList;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -455,7 +457,7 @@
         return "";
     }
 
-    private String getActionStringFromReqType(SsData.RequestType rType) {
+    private static String getActionStringFromReqType(SsData.RequestType rType) {
         switch (rType) {
             case SS_ACTIVATION:
                 return ACTION_ACTIVATE;
@@ -520,6 +522,40 @@
         }
     }
 
+    public static SsData.ServiceType cfReasonToServiceType(int commandInterfaceCFReason) {
+        switch (commandInterfaceCFReason) {
+            case CommandsInterface.CF_REASON_UNCONDITIONAL:
+                return  SsData.ServiceType.SS_CFU;
+            case CommandsInterface.CF_REASON_BUSY:
+                return SsData.ServiceType.SS_CF_BUSY;
+            case CommandsInterface.CF_REASON_NO_REPLY:
+                return SsData.ServiceType.SS_CF_NO_REPLY;
+            case CommandsInterface.CF_REASON_NOT_REACHABLE:
+                return SsData.ServiceType.SS_CF_NOT_REACHABLE;
+            case CommandsInterface.CF_REASON_ALL:
+                return SsData.ServiceType.SS_CF_ALL;
+            case CommandsInterface.CF_REASON_ALL_CONDITIONAL:
+                return SsData.ServiceType.SS_CF_ALL_CONDITIONAL;
+            default:
+                return null;
+        }
+    }
+
+    public static SsData.RequestType cfActionToRequestType(int commandInterfaceCFAction) {
+        switch (commandInterfaceCFAction) {
+            case CommandsInterface.CF_ACTION_DISABLE:
+                return SsData.RequestType.SS_DEACTIVATION;
+            case CommandsInterface.CF_ACTION_ENABLE:
+                return SsData.RequestType.SS_ACTIVATION;
+            case CommandsInterface.CF_ACTION_REGISTRATION:
+                return SsData.RequestType.SS_REGISTRATION;
+            case CommandsInterface.CF_ACTION_ERASURE:
+                return SsData.RequestType.SS_ERASURE;
+            default:
+                return null;
+        }
+    }
+
     @UnsupportedAppUsage
     private static int
     siToServiceClass(String si) {
@@ -623,6 +659,29 @@
         }
     }
 
+    public static SsData.ServiceType cbFacilityToServiceType(String commandInterfaceCBFacility) {
+        switch(commandInterfaceCBFacility) {
+            case CommandsInterface.CB_FACILITY_BAOC:
+                return SsData.ServiceType.SS_BAOC;
+            case CommandsInterface.CB_FACILITY_BAOIC:
+                return SsData.ServiceType.SS_BAOIC;
+            case CommandsInterface.CB_FACILITY_BAOICxH:
+                return SsData.ServiceType.SS_BAOIC_EXC_HOME;
+            case CommandsInterface.CB_FACILITY_BAIC:
+                return SsData.ServiceType.SS_BAIC;
+            case CommandsInterface.CB_FACILITY_BAICr:
+                return SsData.ServiceType.SS_BAIC_ROAMING;
+            case CommandsInterface.CB_FACILITY_BA_ALL:
+                return SsData.ServiceType.SS_ALL_BARRING;
+            case CommandsInterface.CB_FACILITY_BA_MO:
+                return SsData.ServiceType.SS_OUTGOING_BARRING;
+            case CommandsInterface.CB_FACILITY_BA_MT:
+                return SsData.ServiceType.SS_INCOMING_BARRING;
+            default:
+                return null;
+        }
+    }
+
     //***** Constructor
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -892,6 +951,17 @@
         return CommandsInterface.CLIR_DEFAULT;
     }
 
+    public static SsData.RequestType clirModeToRequestType(int commandInterfaceCLIRMode) {
+        switch (commandInterfaceCLIRMode) {
+            case CommandsInterface.CLIR_SUPPRESSION:
+                return SsData.RequestType.SS_ACTIVATION;
+            case CommandsInterface.CLIR_INVOCATION:
+                return SsData.RequestType.SS_DEACTIVATION;
+            default:
+                return null;
+        }
+    }
+
     /**
      * Returns true if the Service Code is FAC to dial as a normal call.
      *
@@ -1099,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");
                 }
@@ -1333,8 +1417,8 @@
         }
     }
     //***** Private instance methods
-
-    private CharSequence getErrorMessage(AsyncResult ar) {
+    @VisibleForTesting
+    public CharSequence getErrorMessage(AsyncResult ar) {
 
         if (ar.exception instanceof CommandException) {
             CommandException.Error err = ((CommandException)(ar.exception)).getCommandError();
@@ -1362,6 +1446,13 @@
             } else if (err == CommandException.Error.OEM_ERROR_1) {
                 Rlog.i(LOG_TAG, "OEM_ERROR_1 USSD_MODIFIED_TO_DIAL_VIDEO");
                 return mContext.getText(com.android.internal.R.string.stk_cc_ussd_to_dial_video);
+            } else if (err == CommandException.Error.REQUEST_NOT_SUPPORTED
+                || err == CommandException.Error.OPERATION_NOT_ALLOWED) {
+                Rlog.i(LOG_TAG, "REQUEST_NOT_SUPPORTED/OPERATION_NOT_ALLOWED");
+                // getResources().getText() is the same as getText(), however getText() is final and
+                // cannot be mocked in tests.
+                return mContext.getResources().getText(
+                        com.android.internal.R.string.mmiErrorNotSupported);
             }
         }
 
@@ -1827,6 +1918,120 @@
         return this.mCallbackReceiver;
     }
 
+    /**
+     * Returns list of control strings for a supplementary service request
+     * as defined in TS 22.030 6.5
+     * @param requestType request type associated with the supplementary service
+     * @param serviceType supplementary service type
+     * @return list of control strings associated with the supplementary service.
+     */
+    public static ArrayList<String> getControlStrings(SsData.RequestType requestType,
+            SsData.ServiceType serviceType) {
+        ArrayList<String> controlStrings = new ArrayList<>();
+        if (requestType == null || serviceType == null) {
+            return controlStrings;
+        }
+
+        String actionStr = getActionStringFromReqType(requestType);
+        switch (serviceType) {
+            case SS_CFU:
+                controlStrings.add(actionStr + SC_CFU);
+                controlStrings.add(actionStr + SC_CF_All);
+                break;
+            case SS_CF_BUSY:
+                controlStrings.add(actionStr + SC_CFB);
+                controlStrings.add(actionStr + SC_CF_All_Conditional);
+                controlStrings.add(actionStr + SC_CF_All);
+                break;
+            case SS_CF_NO_REPLY:
+                controlStrings.add(actionStr + SC_CFNRy);
+                controlStrings.add(actionStr + SC_CF_All_Conditional);
+                controlStrings.add(actionStr + SC_CF_All);
+                break;
+            case SS_CF_NOT_REACHABLE:
+                controlStrings.add(actionStr + SC_CFNR);
+                controlStrings.add(actionStr + SC_CF_All_Conditional);
+                controlStrings.add(actionStr + SC_CF_All);
+                break;
+            case SS_CF_ALL:
+                controlStrings.add(actionStr + SC_CF_All);
+                break;
+            case SS_CF_ALL_CONDITIONAL:
+                controlStrings.add(actionStr + SC_CF_All_Conditional);
+                controlStrings.add(actionStr + SC_CF_All);
+                break;
+            case SS_CLIP:
+                controlStrings.add(actionStr + SC_CLIP);
+                break;
+            case SS_CLIR:
+                controlStrings.add(actionStr + SC_CLIR);
+                break;
+            case SS_WAIT:
+                controlStrings.add(actionStr + SC_WAIT);
+                break;
+            case SS_BAOC:
+                controlStrings.add(actionStr + SC_BAOC);
+                controlStrings.add(actionStr + SC_BA_MO);
+                controlStrings.add(actionStr + SC_BA_ALL);
+                break;
+            case SS_BAOIC:
+                controlStrings.add(actionStr + SC_BAOIC);
+                controlStrings.add(actionStr + SC_BA_MO);
+                controlStrings.add(actionStr + SC_BA_ALL);
+                break;
+            case SS_BAOIC_EXC_HOME:
+                controlStrings.add(actionStr + SC_BAOICxH);
+                controlStrings.add(actionStr + SC_BA_MO);
+                controlStrings.add(actionStr + SC_BA_ALL);
+                break;
+            case SS_BAIC:
+                controlStrings.add(actionStr + SC_BAIC);
+                controlStrings.add(actionStr + SC_BA_MT);
+                controlStrings.add(actionStr + SC_BA_ALL);
+                break;
+            case SS_BAIC_ROAMING:
+                controlStrings.add(actionStr + SC_BAICr);
+                controlStrings.add(actionStr + SC_BA_MT);
+                controlStrings.add(actionStr + SC_BA_ALL);
+                break;
+            case SS_ALL_BARRING:
+                controlStrings.add(actionStr + SC_BA_ALL);
+                break;
+            case SS_OUTGOING_BARRING:
+                controlStrings.add(actionStr + SC_BA_MO);
+                controlStrings.add(actionStr + SC_BA_ALL);
+                break;
+            case SS_INCOMING_BARRING:
+                controlStrings.add(actionStr + SC_BA_MT);
+                controlStrings.add(actionStr + SC_BA_ALL);
+                break;
+        }
+       return controlStrings;
+    }
+
+    /**
+     * Returns control strings for registration of new password as per TS 22.030 6.5.4
+     * @param requestType request type associated with the supplementary service
+     * @param serviceType supplementary service type
+     * @return list of control strings for new password registration.
+     */
+    public static ArrayList<String> getControlStringsForPwd(SsData.RequestType requestType,
+            SsData.ServiceType serviceType) {
+        ArrayList<String> controlStrings = new ArrayList<>();
+        if (requestType == null || serviceType == null) {
+            return controlStrings;
+        }
+
+        controlStrings = getControlStrings(SsData.RequestType.SS_ACTIVATION, serviceType);
+        String actionStr = getActionStringFromReqType(requestType);
+        ArrayList<String> controlStringsPwd = new ArrayList<>();
+        for(String controlString : controlStrings) {
+            // Prepend each control string with **SC_PWD
+            controlStringsPwd.add(actionStr + SC_PWD + controlString);
+        }
+        return controlStringsPwd;
+    }
+
     /***
      * TODO: It would be nice to have a method here that can take in a dialstring and
      * figure out if there is an MMI code embedded within it.  This code would replace
diff --git a/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
index 1f237b2..5b1f36d 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
@@ -120,6 +120,21 @@
     }
 
     @Override
+    protected SmsMessageBase.SubmitPduBase getSubmitPdu(String scAddr, String destAddr,
+            String message, boolean statusReportRequested, SmsHeader smsHeader, int priority,
+            int validityPeriod, int messageRef) {
+        return SMSDispatcherUtil.getSubmitPduGsm(scAddr, destAddr, message, statusReportRequested,
+                validityPeriod, messageRef);
+    }
+
+    @Override
+    protected SmsMessageBase.SubmitPduBase getSubmitPdu(String scAddr, String destAddr,
+            int destPort, byte[] message, boolean statusReportRequested, int messageRef) {
+        return SMSDispatcherUtil.getSubmitPduGsm(scAddr, destAddr, destPort, message,
+                statusReportRequested, messageRef);
+    }
+
+    @Override
     protected TextEncodingDetails calculateLength(CharSequence messageBody, boolean use7bitOnly) {
         return SMSDispatcherUtil.calculateLengthGsm(messageBody, use7bitOnly);
     }
diff --git a/src/java/com/android/internal/telephony/gsm/SuppServiceNotification.java b/src/java/com/android/internal/telephony/gsm/SuppServiceNotification.java
index 41e76ae..396e94c 100644
--- a/src/java/com/android/internal/telephony/gsm/SuppServiceNotification.java
+++ b/src/java/com/android/internal/telephony/gsm/SuppServiceNotification.java
@@ -18,6 +18,8 @@
 
 import android.telephony.PhoneNumberUtils;
 
+import java.util.Arrays;
+
 /**
  * Represents a Supplementary Service Notification received from the network.
  *
@@ -200,7 +202,7 @@
             + (notificationType == 0 ? " originated " : " terminated ")
             + " code: " + code
             + " index: " + index
-            + " history: " + history
+            + " history: " + Arrays.toString(history)
             + " \""
             + PhoneNumberUtils.stringFromStringAndTOA(number, type) + "\" ";
     }
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;
+
+